From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- svl/source/numbers/currencytable.cxx | 37 + svl/source/numbers/numfmuno.cxx | 988 ++++++ svl/source/numbers/numfmuno.hxx | 222 ++ svl/source/numbers/numuno.cxx | 89 + svl/source/numbers/supservs.cxx | 161 + svl/source/numbers/supservs.hxx | 80 + svl/source/numbers/zforfind.cxx | 4288 ++++++++++++++++++++++++ svl/source/numbers/zforfind.hxx | 441 +++ svl/source/numbers/zforlist.cxx | 4972 ++++++++++++++++++++++++++++ svl/source/numbers/zformat.cxx | 6035 ++++++++++++++++++++++++++++++++++ svl/source/numbers/zforscan.cxx | 3322 +++++++++++++++++++ svl/source/numbers/zforscan.hxx | 304 ++ 12 files changed, 20939 insertions(+) create mode 100644 svl/source/numbers/currencytable.cxx create mode 100644 svl/source/numbers/numfmuno.cxx create mode 100644 svl/source/numbers/numfmuno.hxx create mode 100644 svl/source/numbers/numuno.cxx create mode 100644 svl/source/numbers/supservs.cxx create mode 100644 svl/source/numbers/supservs.hxx create mode 100644 svl/source/numbers/zforfind.cxx create mode 100644 svl/source/numbers/zforfind.hxx create mode 100644 svl/source/numbers/zforlist.cxx create mode 100644 svl/source/numbers/zformat.cxx create mode 100644 svl/source/numbers/zforscan.cxx create mode 100644 svl/source/numbers/zforscan.hxx (limited to 'svl/source/numbers') diff --git a/svl/source/numbers/currencytable.cxx b/svl/source/numbers/currencytable.cxx new file mode 100644 index 000000000..84604ecc9 --- /dev/null +++ b/svl/source/numbers/currencytable.cxx @@ -0,0 +1,37 @@ +/* -*- 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/. +*/ + +#include + +NfCurrencyTable::iterator NfCurrencyTable::begin() +{ + return maData.begin(); +} + +NfCurrencyEntry& NfCurrencyTable::operator[] ( size_t i ) +{ + return maData[i]; +} + +const NfCurrencyEntry& NfCurrencyTable::operator[] ( size_t i ) const +{ + return maData[i]; +} + +size_t NfCurrencyTable::size() const +{ + return maData.size(); +} + +void NfCurrencyTable::insert(const iterator& it, NfCurrencyEntry p) +{ + maData.insert(it, std::move(p)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/numbers/numfmuno.cxx b/svl/source/numbers/numfmuno.cxx new file mode 100644 index 000000000..b1e9adef8 --- /dev/null +++ b/svl/source/numbers/numfmuno.cxx @@ -0,0 +1,988 @@ +/* -*- 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "numfmuno.hxx" +#include +#include +#include +#include +#include +#include + +using namespace com::sun::star; + +constexpr OUStringLiteral PROPERTYNAME_FMTSTR = u"FormatString"; +constexpr OUStringLiteral PROPERTYNAME_LOCALE = u"Locale"; +constexpr OUStringLiteral PROPERTYNAME_TYPE = u"Type"; +constexpr OUStringLiteral PROPERTYNAME_COMMENT = u"Comment"; +constexpr OUStringLiteral PROPERTYNAME_CURREXT = u"CurrencyExtension"; +constexpr OUStringLiteral PROPERTYNAME_CURRSYM = u"CurrencySymbol"; +constexpr OUStringLiteral PROPERTYNAME_CURRABB = u"CurrencyAbbreviation"; +constexpr OUStringLiteral PROPERTYNAME_DECIMALS = u"Decimals"; +constexpr OUStringLiteral PROPERTYNAME_LEADING = u"LeadingZeros"; +constexpr OUStringLiteral PROPERTYNAME_NEGRED = u"NegativeRed"; +constexpr OUStringLiteral PROPERTYNAME_STDFORM = u"StandardFormat"; +constexpr OUStringLiteral PROPERTYNAME_THOUS = u"ThousandsSeparator"; +constexpr OUStringLiteral PROPERTYNAME_USERDEF = u"UserDefined"; + +constexpr OUStringLiteral PROPERTYNAME_NOZERO = u"NoZero"; +constexpr OUStringLiteral PROPERTYNAME_NULLDATE = u"NullDate"; +constexpr OUStringLiteral PROPERTYNAME_STDDEC = u"StandardDecimals"; +constexpr OUStringLiteral PROPERTYNAME_TWODIGIT = u"TwoDigitDateStart"; + +// All without a Which-ID, Map only for PropertySetInfo + +static const SfxItemPropertyMapEntry* lcl_GetNumberFormatPropertyMap() +{ + static const SfxItemPropertyMapEntry aNumberFormatPropertyMap_Impl[] = + { + {PROPERTYNAME_FMTSTR, 0, cppu::UnoType::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0}, + {PROPERTYNAME_LOCALE, 0, cppu::UnoType::get(),beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0}, + {PROPERTYNAME_TYPE, 0, cppu::UnoType::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0}, + {PROPERTYNAME_COMMENT, 0, cppu::UnoType::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0}, + {PROPERTYNAME_CURREXT, 0, cppu::UnoType::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0}, + {PROPERTYNAME_CURRSYM, 0, cppu::UnoType::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0}, + {PROPERTYNAME_DECIMALS, 0, cppu::UnoType::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0}, + {PROPERTYNAME_LEADING, 0, cppu::UnoType::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0}, + {PROPERTYNAME_NEGRED, 0, cppu::UnoType::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0}, + {PROPERTYNAME_STDFORM, 0, cppu::UnoType::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0}, + {PROPERTYNAME_THOUS, 0, cppu::UnoType::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0}, + {PROPERTYNAME_USERDEF, 0, cppu::UnoType::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0}, + {PROPERTYNAME_CURRABB, 0, cppu::UnoType::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0}, + { u"", 0, css::uno::Type(), 0, 0 } + }; + return aNumberFormatPropertyMap_Impl; +} + +static const SfxItemPropertyMapEntry* lcl_GetNumberSettingsPropertyMap() +{ + static const SfxItemPropertyMapEntry aNumberSettingsPropertyMap_Impl[] = + { + {PROPERTYNAME_NOZERO, 0, cppu::UnoType::get(), beans::PropertyAttribute::BOUND, 0}, + {PROPERTYNAME_NULLDATE, 0, cppu::UnoType::get(), beans::PropertyAttribute::BOUND, 0}, + {PROPERTYNAME_STDDEC, 0, cppu::UnoType::get(), beans::PropertyAttribute::BOUND, 0}, + {PROPERTYNAME_TWODIGIT, 0, cppu::UnoType::get(), beans::PropertyAttribute::BOUND, 0}, + { u"", 0, css::uno::Type(), 0, 0 } + }; + return aNumberSettingsPropertyMap_Impl; +} + +static LanguageType lcl_GetLanguage( const lang::Locale& rLocale ) +{ + LanguageType eRet = LanguageTag::convertToLanguageTypeWithFallback( rLocale ); + if ( eRet == LANGUAGE_NONE ) + eRet = LANGUAGE_SYSTEM; //! or throw an exception? + + return eRet; +} + +SvNumberFormatterServiceObj::SvNumberFormatterServiceObj() +{ +} + +SvNumberFormatterServiceObj::~SvNumberFormatterServiceObj() +{ +} + +// XNumberFormatter + +void SAL_CALL SvNumberFormatterServiceObj::attachNumberFormatsSupplier( const uno::Reference& _xSupplier ) +{ + ::rtl::Reference< SvNumberFormatsSupplierObj > xAutoReleaseOld; + + // SYNCHRONIZED -> + { + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + + SvNumberFormatsSupplierObj* pNew = comphelper::getFromUnoTunnel( _xSupplier ); + if (!pNew) + throw uno::RuntimeException(); // wrong object + + xAutoReleaseOld = xSupplier; + + xSupplier = pNew; + m_aMutex = xSupplier->getSharedMutex(); + } + // <- SYNCHRONIZED +} + +uno::Reference SAL_CALL SvNumberFormatterServiceObj::getNumberFormatsSupplier() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + return xSupplier; +} + +sal_Int32 SAL_CALL SvNumberFormatterServiceObj::detectNumberFormat( sal_Int32 nKey, const OUString& aString ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr; + if (!pFormatter) + throw uno::RuntimeException(); + + sal_uInt32 nUKey = nKey; + double fValue = 0.0; + if ( !pFormatter->IsNumberFormat(aString, nUKey, fValue) ) + throw util::NotNumericException(); + + return nUKey; +} + +double SAL_CALL SvNumberFormatterServiceObj::convertStringToNumber( sal_Int32 nKey, const OUString& aString ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr; + if (!pFormatter) + throw uno::RuntimeException(); + + sal_uInt32 nUKey = nKey; + double fValue = 0.0; + if ( !pFormatter->IsNumberFormat(aString, nUKey, fValue) ) + throw util::NotNumericException(); + + return fValue; +} + +OUString SAL_CALL SvNumberFormatterServiceObj::convertNumberToString( sal_Int32 nKey, double fValue ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + OUString aRet; + SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr; + if (!pFormatter) + throw uno::RuntimeException(); + + const Color* pColor = nullptr; + pFormatter->GetOutputString(fValue, nKey, aRet, &pColor); + + return aRet; +} + +sal_Int32 SAL_CALL SvNumberFormatterServiceObj::queryColorForNumber( sal_Int32 nKey, + double fValue, + sal_Int32 aDefaultColor ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + util::Color nRet = aDefaultColor; // color = sal_Int32 + SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr; + if (!pFormatter) + throw uno::RuntimeException(); + + OUString aStr; + const Color* pColor = nullptr; + pFormatter->GetOutputString(fValue, nKey, aStr, &pColor); + if (pColor) + nRet = sal_uInt32(*pColor); + // Else keep Default + + return nRet; +} + +OUString SAL_CALL SvNumberFormatterServiceObj::formatString( sal_Int32 nKey, + const OUString& aString ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + OUString aRet; + SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr; + if (!pFormatter) + { + throw uno::RuntimeException(); + } + + const Color* pColor = nullptr; + pFormatter->GetOutputString(aString, nKey, aRet, &pColor); + + return aRet; +} + +sal_Int32 SAL_CALL SvNumberFormatterServiceObj::queryColorForString( sal_Int32 nKey, + const OUString& aString, + sal_Int32 aDefaultColor ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + util::Color nRet = aDefaultColor; // color = sal_Int32 + SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr; + if (!pFormatter) + { + throw uno::RuntimeException(); + } + + OUString aStr; + const Color* pColor = nullptr; + pFormatter->GetOutputString(aString, nKey, aStr, &pColor); + if (pColor) + { + nRet = sal_uInt32(*pColor); + } + // Else keep Default + + return nRet; +} + +OUString SAL_CALL SvNumberFormatterServiceObj::getInputString( sal_Int32 nKey, double fValue ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + OUString aRet; + SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr; + if (!pFormatter) + throw uno::RuntimeException(); + + pFormatter->GetInputLineString(fValue, nKey, aRet); + + return aRet; +} + +// XNumberFormatPreviewer + +OUString SAL_CALL SvNumberFormatterServiceObj::convertNumberToPreviewString( const OUString& aFormat, + double fValue, + const lang::Locale& nLocale, + sal_Bool bAllowEnglish ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + OUString aRet; + SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr; + if (!pFormatter) + throw uno::RuntimeException(); + + LanguageType eLang = lcl_GetLanguage( nLocale ); + const Color* pColor = nullptr; + + bool bOk; + if ( bAllowEnglish ) + bOk = pFormatter->GetPreviewStringGuess( aFormat, fValue, aRet, &pColor, eLang ); + else + bOk = pFormatter->GetPreviewString( aFormat, fValue, aRet, &pColor, eLang ); + + if (!bOk) + throw util::MalformedNumberFormatException(); + + return aRet; +} + +sal_Int32 SAL_CALL SvNumberFormatterServiceObj::queryPreviewColorForNumber( const OUString& aFormat, + double fValue, + const lang::Locale& nLocale, + sal_Bool bAllowEnglish, + sal_Int32 aDefaultColor ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + util::Color nRet = aDefaultColor; // color = sal_Int32 + SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr; + if (!pFormatter) + throw uno::RuntimeException(); + + OUString aOutString; + LanguageType eLang = lcl_GetLanguage( nLocale ); + const Color* pColor = nullptr; + + bool bOk; + if ( bAllowEnglish ) + bOk = pFormatter->GetPreviewStringGuess( aFormat, fValue, aOutString, &pColor, eLang ); + else + bOk = pFormatter->GetPreviewString( aFormat, fValue, aOutString, &pColor, eLang ); + + if (!bOk) + throw util::MalformedNumberFormatException(); + + if (pColor) + nRet = sal_uInt32(*pColor); + // Else keep Default + + return nRet; +} + +// XServiceInfo + +OUString SAL_CALL SvNumberFormatterServiceObj::getImplementationName() +{ + return "com.sun.star.uno.util.numbers.SvNumberFormatterServiceObject"; +} + +sal_Bool SAL_CALL SvNumberFormatterServiceObj::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService( this, ServiceName ); +} + +uno::Sequence SAL_CALL SvNumberFormatterServiceObj::getSupportedServiceNames() +{ + return { "com.sun.star.util.NumberFormatter" }; +} + +SvNumberFormatsObj::SvNumberFormatsObj( SvNumberFormatsSupplierObj& _rParent, ::comphelper::SharedMutex _aMutex ) + :m_xSupplier( &_rParent ) + ,m_aMutex(std::move( _aMutex )) +{ +} + +SvNumberFormatsObj::~SvNumberFormatsObj() +{ +} + +// XNumberFormats + +uno::Reference SAL_CALL SvNumberFormatsObj::getByKey( sal_Int32 nKey ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter(); + const SvNumberformat* pFormat = pFormatter ? pFormatter->GetEntry(nKey) : nullptr; + if (!pFormat) + throw uno::RuntimeException(); + + return new SvNumberFormatObj( *m_xSupplier, nKey, m_aMutex ); +} + +uno::Sequence SAL_CALL SvNumberFormatsObj::queryKeys( sal_Int16 nType, + const lang::Locale& nLocale, + sal_Bool bCreate ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter(); + if ( !pFormatter ) + throw uno::RuntimeException(); + + sal_uInt32 nIndex = 0; + LanguageType eLang = lcl_GetLanguage( nLocale ); + SvNumberFormatTable& rTable = bCreate ? + pFormatter->ChangeCL( static_cast(nType), nIndex, eLang ) : + pFormatter->GetEntryTable( static_cast(nType), nIndex, eLang ); + sal_uInt32 nCount = rTable.size(); + uno::Sequence aSeq(nCount); + sal_Int32* pAry = aSeq.getArray(); + sal_uInt32 i=0; + for (const auto& rEntry : rTable) + { + pAry[i] = rEntry.first; + ++i; + } + return aSeq; +} + +sal_Int32 SAL_CALL SvNumberFormatsObj::queryKey( const OUString& aFormat, + const lang::Locale& nLocale, + sal_Bool bScan ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter(); + if (!pFormatter) + throw uno::RuntimeException(); + + LanguageType eLang = lcl_GetLanguage( nLocale ); + if (bScan) + { + //! FIXME: Something still needs to happen here ... + } + sal_uInt32 nRet = pFormatter->GetEntryKey( aFormat, eLang ); + if (nRet == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + // This seems to be a workaround for what maybe the bScan option was + // intended for? Tokenize the format code? + + // The format string based search is vague and fuzzy, as it is case + // sensitive, but the format string is only half way cased, the + // keywords (except the "General" keyword) are uppercased and literals + // of course are not. Clients using this queryKey() and if not + // successful addNew() may still fail if the result of PutEntry() is + // false because the format actually existed, just with a different + // casing. The only clean way is to just use PutEntry() and ignore the + // duplicate case, which clients can't because the API doesn't provide + // the information. + // Try just another possibilty here, without any guarantee. + + // Use only ASCII upper, because keywords are only those. + // Do not transliterate any quoted literals. + const sal_Int32 nLen = aFormat.getLength(); + OUStringBuffer aBuf(0); + sal_Unicode* p = aBuf.appendUninitialized( nLen + 1); + memcpy( p, aFormat.getStr(), (nLen + 1) * sizeof(sal_Unicode)); // including 0-char + aBuf.setLength( nLen); + assert(p == aBuf.getStr()); + sal_Unicode const * const pStop = p + aBuf.getLength(); + bool bQuoted = false; + for ( ; p < pStop; ++p) + { + if (bQuoted) + { + // Format codes don't have embedded doubled quotes, i.e. "a""b" + // is two literals displayed as `ab`. + if (*p == '"') + bQuoted = false; + } + else if (*p == '"') + bQuoted = true; + else if ('a' <= *p && *p <= 'z') + *p -= 0x20; // upper + else if (*p == '\\') + ++p; // skip escaped next char + // Theoretically that should cater for UTF-32 with + // iterateCodePoints(), but such character would not match any + // of [a-z\"] in its UTF-16 units. + } + nRet = pFormatter->GetEntryKey( aBuf, eLang ); + } + return static_cast(nRet); +} + +sal_Int32 SAL_CALL SvNumberFormatsObj::addNew( const OUString& aFormat, + const lang::Locale& nLocale ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + sal_Int32 nRet = 0; + SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter(); + if (!pFormatter) + throw uno::RuntimeException(); + + OUString aFormStr = aFormat; + LanguageType eLang = lcl_GetLanguage( nLocale ); + sal_uInt32 nKey = 0; + sal_Int32 nCheckPos = 0; + SvNumFormatType nType = SvNumFormatType::ALL; + bool bOk = pFormatter->PutEntry( aFormStr, nCheckPos, nType, nKey, eLang ); + if (bOk) + nRet = nKey; + else if (nCheckPos) + { + throw util::MalformedNumberFormatException(); // Invalid Format + } + else if (aFormStr != aFormat) + { + // The format exists but with a different format code string, and if it + // was only uppercase vs lowercase keywords; but also syntax extensions + // are possible like resulting embedded LCIDs and what not the client + // doesn't know about. Silently accept instead of throwing an error. + nRet = nKey; + } + else + throw uno::RuntimeException(); // Other error (e.g. already added) + + return nRet; +} + +sal_Int32 SAL_CALL SvNumberFormatsObj::addNewConverted( const OUString& aFormat, + const lang::Locale& nLocale, + const lang::Locale& nNewLocale ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + sal_Int32 nRet = 0; + SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter(); + if (!pFormatter) + throw uno::RuntimeException(); + + OUString aFormStr = aFormat; + LanguageType eLang = lcl_GetLanguage( nLocale ); + LanguageType eNewLang = lcl_GetLanguage( nNewLocale ); + sal_uInt32 nKey = 0; + sal_Int32 nCheckPos = 0; + SvNumFormatType nType = SvNumFormatType::ALL; + // This is used also when reading OOXML documents, there's no indicator + // whether to convert date particle order as well, so don't. See tdf#119013 + bool bOk = pFormatter->PutandConvertEntry( aFormStr, nCheckPos, nType, nKey, eLang, eNewLang, false); + if (bOk || nKey > 0) + nRet = nKey; + else if (nCheckPos) + { + throw util::MalformedNumberFormatException(); // Invalid format + } + else + throw uno::RuntimeException(); // Other error (e.g. already added) + + return nRet; +} + +void SAL_CALL SvNumberFormatsObj::removeByKey( sal_Int32 nKey ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter(); + + if (pFormatter) + { + pFormatter->DeleteEntry(nKey); + } +} + +OUString SAL_CALL SvNumberFormatsObj::generateFormat( sal_Int32 nBaseKey, + const lang::Locale& nLocale, + sal_Bool bThousands, + sal_Bool bRed, sal_Int16 nDecimals, + sal_Int16 nLeading ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter(); + if (!pFormatter) + throw uno::RuntimeException(); + + LanguageType eLang = lcl_GetLanguage( nLocale ); + OUString aRet = pFormatter->GenerateFormat(nBaseKey, eLang, bThousands, bRed, nDecimals, nLeading); + return aRet; +} + +// XNumberFormatTypes + +sal_Int32 SAL_CALL SvNumberFormatsObj::getStandardIndex( const lang::Locale& nLocale ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter(); + if (!pFormatter) + throw uno::RuntimeException(); + + LanguageType eLang = lcl_GetLanguage( nLocale ); + sal_Int32 nRet = pFormatter->GetStandardIndex(eLang); + return nRet; +} + +sal_Int32 SAL_CALL SvNumberFormatsObj::getStandardFormat( sal_Int16 nType, const lang::Locale& nLocale ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter(); + if (!pFormatter) + throw uno::RuntimeException(); + + LanguageType eLang = lcl_GetLanguage( nLocale ); + // Mask out "defined" bit, so type from an existing number format + // can directly be used for getStandardFormat + SvNumFormatType nType2 = static_cast(nType); + nType2 &= ~SvNumFormatType::DEFINED; + sal_Int32 nRet = pFormatter->GetStandardFormat(nType2, eLang); + return nRet; +} + +sal_Int32 SAL_CALL SvNumberFormatsObj::getFormatIndex( sal_Int16 nIndex, const lang::Locale& nLocale ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter(); + if (!pFormatter) + throw uno::RuntimeException(); + + LanguageType eLang = lcl_GetLanguage( nLocale ); + sal_Int32 nRet = pFormatter->GetFormatIndex( static_cast(nIndex), eLang ); + return nRet; +} + +sal_Bool SAL_CALL SvNumberFormatsObj::isTypeCompatible( sal_Int16 nOldType, sal_Int16 nNewType ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + return SvNumberFormatter::IsCompatible( static_cast(nOldType), static_cast(nNewType) ); +} + +sal_Int32 SAL_CALL SvNumberFormatsObj::getFormatForLocale( sal_Int32 nKey, const lang::Locale& nLocale ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter(); + if (!pFormatter) + throw uno::RuntimeException(); + + LanguageType eLang = lcl_GetLanguage( nLocale ); + sal_Int32 nRet = pFormatter->GetFormatForLanguageIfBuiltIn(nKey, eLang); + return nRet; +} + +// XServiceInfo + +OUString SAL_CALL SvNumberFormatsObj::getImplementationName() +{ + return "SvNumberFormatsObj"; +} + +sal_Bool SAL_CALL SvNumberFormatsObj::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService( this, ServiceName ); +} + +uno::Sequence SAL_CALL SvNumberFormatsObj::getSupportedServiceNames() +{ + return { "com.sun.star.util.NumberFormats" }; +} + +SvNumberFormatObj::SvNumberFormatObj( SvNumberFormatsSupplierObj& rParent, sal_uLong nK, ::comphelper::SharedMutex _aMutex ) + :m_xSupplier( &rParent ) + ,nKey( nK ) + ,m_aMutex(std::move( _aMutex )) +{ +} + +SvNumberFormatObj::~SvNumberFormatObj() +{ +} + +// XPropertySet + +uno::Reference SAL_CALL SvNumberFormatObj::getPropertySetInfo() +{ + static uno::Reference aRef = + new SfxItemPropertySetInfo( lcl_GetNumberFormatPropertyMap() ); + return aRef; +} + +void SAL_CALL SvNumberFormatObj::setPropertyValue( const OUString&, + const uno::Any& ) +{ + throw beans::UnknownPropertyException(); // Everything is read-only +} + +uno::Any SAL_CALL SvNumberFormatObj::getPropertyValue( const OUString& aPropertyName ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + uno::Any aRet; + SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter(); + const SvNumberformat* pFormat = pFormatter ? pFormatter->GetEntry(nKey) : nullptr; + if (!pFormat) + throw uno::RuntimeException(); + + bool bThousand, bRed; + sal_uInt16 nDecimals, nLeading; + + if (aPropertyName == PROPERTYNAME_FMTSTR) + { + aRet <<= pFormat->GetFormatstring(); + } + else if (aPropertyName == PROPERTYNAME_LOCALE) + { + lang::Locale aLocale( LanguageTag::convertToLocale( pFormat->GetLanguage(), false)); + aRet <<= aLocale; + } + else if (aPropertyName == PROPERTYNAME_TYPE) + { + aRet <<= static_cast( pFormat->GetType() ); + } + else if (aPropertyName == PROPERTYNAME_COMMENT) + { + aRet <<= pFormat->GetComment(); + } + else if (aPropertyName == PROPERTYNAME_STDFORM) + { + //! Pass through SvNumberformat Member bStandard? + aRet <<= ( ( nKey % SV_COUNTRY_LANGUAGE_OFFSET ) == 0 ); + } + else if (aPropertyName == PROPERTYNAME_USERDEF) + { + aRet <<= bool( pFormat->GetType() & SvNumFormatType::DEFINED ); + } + else if (aPropertyName == PROPERTYNAME_DECIMALS) + { + pFormat->GetFormatSpecialInfo( bThousand, bRed, nDecimals, nLeading ); + aRet <<= static_cast(nDecimals); + } + else if (aPropertyName == PROPERTYNAME_LEADING) + { + pFormat->GetFormatSpecialInfo( bThousand, bRed, nDecimals, nLeading ); + aRet <<= static_cast(nLeading); + } + else if (aPropertyName == PROPERTYNAME_NEGRED) + { + pFormat->GetFormatSpecialInfo( bThousand, bRed, nDecimals, nLeading ); + aRet <<= bRed; + } + else if (aPropertyName == PROPERTYNAME_THOUS) + { + pFormat->GetFormatSpecialInfo( bThousand, bRed, nDecimals, nLeading ); + aRet <<= bThousand; + } + else if (aPropertyName == PROPERTYNAME_CURRSYM) + { + OUString aSymbol, aExt; + pFormat->GetNewCurrencySymbol( aSymbol, aExt ); + aRet <<= aSymbol; + } + else if (aPropertyName == PROPERTYNAME_CURREXT) + { + OUString aSymbol, aExt; + pFormat->GetNewCurrencySymbol( aSymbol, aExt ); + aRet <<= aExt; + } + else if (aPropertyName == PROPERTYNAME_CURRABB) + { + OUString aSymbol, aExt; + bool bBank = false; + pFormat->GetNewCurrencySymbol( aSymbol, aExt ); + const NfCurrencyEntry* pCurr = SvNumberFormatter::GetCurrencyEntry( bBank, + aSymbol, aExt, pFormat->GetLanguage() ); + if ( pCurr ) + aRet <<= pCurr->GetBankSymbol(); + else + aRet <<= OUString(); + } + else + throw beans::UnknownPropertyException(aPropertyName); + + return aRet; +} + +void SAL_CALL SvNumberFormatObj::addPropertyChangeListener( const OUString&, + const uno::Reference&) +{ + OSL_FAIL("not implemented"); +} + +void SAL_CALL SvNumberFormatObj::removePropertyChangeListener( const OUString&, + const uno::Reference&) +{ + OSL_FAIL("not implemented"); +} + +void SAL_CALL SvNumberFormatObj::addVetoableChangeListener( const OUString&, + const uno::Reference&) +{ + OSL_FAIL("not implemented"); +} + +void SAL_CALL SvNumberFormatObj::removeVetoableChangeListener( const OUString&, + const uno::Reference&) +{ + OSL_FAIL("not implemented"); +} + +// XPropertyAccess + +uno::Sequence SAL_CALL SvNumberFormatObj::getPropertyValues() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter(); + const SvNumberformat* pFormat = pFormatter ? pFormatter->GetEntry(nKey) : nullptr; + if (!pFormat) + throw uno::RuntimeException(); + + OUString aSymbol, aExt; + OUString aAbb; + bool bBank = false; + pFormat->GetNewCurrencySymbol( aSymbol, aExt ); + const NfCurrencyEntry* pCurr = SvNumberFormatter::GetCurrencyEntry( bBank, + aSymbol, aExt, pFormat->GetLanguage() ); + if ( pCurr ) + aAbb = pCurr->GetBankSymbol(); + + OUString aFmtStr = pFormat->GetFormatstring(); + OUString aComment = pFormat->GetComment(); + bool bStandard = ( ( nKey % SV_COUNTRY_LANGUAGE_OFFSET ) == 0 ); + //! Pass through SvNumberformat Member bStandard? + bool bUserDef( pFormat->GetType() & SvNumFormatType::DEFINED ); + bool bThousand, bRed; + sal_uInt16 nDecimals, nLeading; + pFormat->GetFormatSpecialInfo( bThousand, bRed, nDecimals, nLeading ); + lang::Locale aLocale( LanguageTag( pFormat->GetLanguage()).getLocale()); + + uno::Sequence aSeq( comphelper::InitPropertySequence({ + { PROPERTYNAME_FMTSTR, uno::Any(aFmtStr) }, + { PROPERTYNAME_LOCALE, uno::Any(aLocale) }, + { PROPERTYNAME_TYPE, uno::Any(sal_Int16( pFormat->GetType() )) }, + { PROPERTYNAME_COMMENT, uno::Any(aComment) }, + { PROPERTYNAME_STDFORM, uno::Any(bStandard) }, + { PROPERTYNAME_USERDEF, uno::Any(bUserDef) }, + { PROPERTYNAME_DECIMALS, uno::Any(sal_Int16( nDecimals )) }, + { PROPERTYNAME_LEADING, uno::Any(sal_Int16( nLeading )) }, + { PROPERTYNAME_NEGRED, uno::Any(bRed) }, + { PROPERTYNAME_THOUS, uno::Any(bThousand) }, + { PROPERTYNAME_CURRSYM, uno::Any(aSymbol) }, + { PROPERTYNAME_CURREXT, uno::Any(aExt) }, + { PROPERTYNAME_CURRABB, uno::Any(aAbb) } + })); + + return aSeq; +} + +void SAL_CALL SvNumberFormatObj::setPropertyValues( const uno::Sequence& ) +{ + throw beans::UnknownPropertyException(); // Everything is read-only +} + +// XServiceInfo + +OUString SAL_CALL SvNumberFormatObj::getImplementationName() +{ + return "SvNumberFormatObj"; +} + +sal_Bool SAL_CALL SvNumberFormatObj::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService( this, ServiceName ); +} + +uno::Sequence SAL_CALL SvNumberFormatObj::getSupportedServiceNames() +{ + return { "com.sun.star.util.NumberFormatProperties" }; +} + +SvNumberFormatSettingsObj::SvNumberFormatSettingsObj( SvNumberFormatsSupplierObj& rParent, ::comphelper::SharedMutex _aMutex ) + :m_xSupplier( &rParent ) + ,m_aMutex(std::move( _aMutex )) +{ +} + +SvNumberFormatSettingsObj::~SvNumberFormatSettingsObj() +{ +} + +// XPropertySet + +uno::Reference SAL_CALL SvNumberFormatSettingsObj::getPropertySetInfo() +{ + static uno::Reference aRef = + new SfxItemPropertySetInfo( lcl_GetNumberSettingsPropertyMap() ); + return aRef; +} + +void SAL_CALL SvNumberFormatSettingsObj::setPropertyValue( const OUString& aPropertyName, + const uno::Any& aValue ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter(); + if (!pFormatter) + throw uno::RuntimeException(); + + if (aPropertyName == PROPERTYNAME_NOZERO) + { + // operator >>= shouldn't be used for bool (?) + if ( auto b = o3tl::tryAccess(aValue) ) + pFormatter->SetNoZero( *b ); + } + else if (aPropertyName == PROPERTYNAME_NULLDATE) + { + util::Date aDate; + if ( aValue >>= aDate ) + pFormatter->ChangeNullDate( aDate.Day, aDate.Month, aDate.Year ); + } + else if (aPropertyName == PROPERTYNAME_STDDEC) + { + sal_Int16 nInt16 = sal_Int16(); + if ( aValue >>= nInt16 ) + pFormatter->ChangeStandardPrec( nInt16 ); + } + else if (aPropertyName == PROPERTYNAME_TWODIGIT) + { + sal_Int16 nInt16 = sal_Int16(); + if ( aValue >>= nInt16 ) + pFormatter->SetYear2000( nInt16 ); + } + else + throw beans::UnknownPropertyException(aPropertyName); + +} + +uno::Any SAL_CALL SvNumberFormatSettingsObj::getPropertyValue( const OUString& aPropertyName ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + uno::Any aRet; + SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter(); + if (!pFormatter) + throw uno::RuntimeException(); + + if (aPropertyName == PROPERTYNAME_NOZERO) + { + aRet <<= pFormatter->GetNoZero(); + } + else if (aPropertyName == PROPERTYNAME_NULLDATE) + { + const Date& rDate = pFormatter->GetNullDate(); + aRet <<= rDate.GetUNODate(); + } + else if (aPropertyName == PROPERTYNAME_STDDEC) + aRet <<= static_cast( pFormatter->GetStandardPrec() ); + else if (aPropertyName == PROPERTYNAME_TWODIGIT) + aRet <<= static_cast( pFormatter->GetYear2000() ); + else + throw beans::UnknownPropertyException(aPropertyName); + + return aRet; +} + +void SAL_CALL SvNumberFormatSettingsObj::addPropertyChangeListener( const OUString&, + const uno::Reference&) +{ + OSL_FAIL("not implemented"); +} + +void SAL_CALL SvNumberFormatSettingsObj::removePropertyChangeListener( const OUString&, + const uno::Reference&) +{ + OSL_FAIL("not implemented"); +} + +void SAL_CALL SvNumberFormatSettingsObj::addVetoableChangeListener( const OUString&, + const uno::Reference&) +{ + OSL_FAIL("not implemented"); +} + +void SAL_CALL SvNumberFormatSettingsObj::removeVetoableChangeListener( const OUString&, + const uno::Reference&) +{ + OSL_FAIL("not implemented"); +} + +// XServiceInfo + +OUString SAL_CALL SvNumberFormatSettingsObj::getImplementationName() +{ + return "SvNumberFormatSettingsObj"; +} + +sal_Bool SAL_CALL SvNumberFormatSettingsObj::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService( this, ServiceName ); +} + +uno::Sequence SAL_CALL SvNumberFormatSettingsObj::getSupportedServiceNames() +{ + return { "com.sun.star.util.NumberFormatSettings" }; +} + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_uno_util_numbers_SvNumberFormatterServiceObject_get_implementation(css::uno::XComponentContext*, + css::uno::Sequence const &) +{ + return cppu::acquire(new SvNumberFormatterServiceObj()); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/numbers/numfmuno.hxx b/svl/source/numbers/numfmuno.hxx new file mode 100644 index 000000000..f88c2e642 --- /dev/null +++ b/svl/source/numbers/numfmuno.hxx @@ -0,0 +1,222 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVL_SOURCE_NUMBERS_NUMFMUNO_HXX +#define INCLUDED_SVL_SOURCE_NUMBERS_NUMFMUNO_HXX + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class SvNumberFormatsSupplierObj; + + +/** + * SvNumberFormatterServiceObj is registered globally as a Service + */ +class SvNumberFormatterServiceObj : public cppu::WeakImplHelper< + css::util::XNumberFormatter2, + css::lang::XServiceInfo> +{ +private: + ::rtl::Reference< SvNumberFormatsSupplierObj > xSupplier; + mutable ::comphelper::SharedMutex m_aMutex; + +public: + SvNumberFormatterServiceObj(); + virtual ~SvNumberFormatterServiceObj() override; + + // XNumberFormatter + virtual void SAL_CALL attachNumberFormatsSupplier( + const css::uno::Reference< css::util::XNumberFormatsSupplier >& xSupplier ) override; + virtual css::uno::Reference< css::util::XNumberFormatsSupplier > + SAL_CALL getNumberFormatsSupplier() override; + virtual sal_Int32 SAL_CALL detectNumberFormat( sal_Int32 nKey, const OUString& aString ) override; + virtual double SAL_CALL convertStringToNumber( sal_Int32 nKey, const OUString& aString ) override; + virtual OUString SAL_CALL convertNumberToString( sal_Int32 nKey, double fValue ) override; + virtual sal_Int32 SAL_CALL queryColorForNumber( sal_Int32 nKey, + double fValue, sal_Int32 aDefaultColor ) override; + virtual OUString SAL_CALL formatString( sal_Int32 nKey, const OUString& aString ) override; + virtual sal_Int32 SAL_CALL queryColorForString( sal_Int32 nKey, + const OUString& aString, + sal_Int32 aDefaultColor ) override; + virtual OUString SAL_CALL getInputString( sal_Int32 nKey, double fValue ) override; + + // XNumberFormatPreviewer + virtual OUString SAL_CALL convertNumberToPreviewString( + const OUString& aFormat, double fValue, + const css::lang::Locale& nLocale, sal_Bool bAllowEnglish ) override; + virtual sal_Int32 SAL_CALL queryPreviewColorForNumber( + const OUString& aFormat, double fValue, + const css::lang::Locale& nLocale, sal_Bool bAllowEnglish, + sal_Int32 aDefaultColor ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; +}; + + +class SvNumberFormatsObj : public cppu::WeakImplHelper< + css::util::XNumberFormats, + css::util::XNumberFormatTypes, + css::lang::XServiceInfo> +{ +private: + rtl::Reference m_xSupplier; + mutable ::comphelper::SharedMutex m_aMutex; + +public: + SvNumberFormatsObj(SvNumberFormatsSupplierObj& pParent, ::comphelper::SharedMutex _aMutex); + virtual ~SvNumberFormatsObj() override; + + + // XNumberFormats + virtual css::uno::Reference< css::beans::XPropertySet > SAL_CALL + getByKey( sal_Int32 nKey ) override; + virtual css::uno::Sequence< sal_Int32 > SAL_CALL queryKeys( sal_Int16 nType, + const css::lang::Locale& nLocale, sal_Bool bCreate ) override; + virtual sal_Int32 SAL_CALL queryKey( const OUString& aFormat, + const css::lang::Locale& nLocale, sal_Bool bScan ) override; + virtual sal_Int32 SAL_CALL addNew( const OUString& aFormat, + const css::lang::Locale& nLocale ) override; + virtual sal_Int32 SAL_CALL addNewConverted( const OUString& aFormat, + const css::lang::Locale& nLocale, + const css::lang::Locale& nNewLocale ) override; + virtual void SAL_CALL removeByKey( sal_Int32 nKey ) override; + virtual OUString SAL_CALL generateFormat( sal_Int32 nBaseKey, + const css::lang::Locale& nLocale, sal_Bool bThousands, + sal_Bool bRed, sal_Int16 nDecimals, sal_Int16 nLeading ) override; + + // XNumberFormatTypes + virtual sal_Int32 SAL_CALL getStandardIndex( const css::lang::Locale& nLocale ) override; + virtual sal_Int32 SAL_CALL getStandardFormat( sal_Int16 nType, + const css::lang::Locale& nLocale ) override; + virtual sal_Int32 SAL_CALL getFormatIndex( sal_Int16 nIndex, + const css::lang::Locale& nLocale ) override; + virtual sal_Bool SAL_CALL isTypeCompatible( sal_Int16 nOldType, sal_Int16 nNewType ) override; + virtual sal_Int32 SAL_CALL getFormatForLocale( sal_Int32 nKey, + const css::lang::Locale& nLocale ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; +}; + + +class SvNumberFormatObj : public cppu::WeakImplHelper< + css::beans::XPropertySet, + css::beans::XPropertyAccess, + css::lang::XServiceInfo> +{ +private: + rtl::Reference + m_xSupplier; + sal_uLong nKey; + mutable ::comphelper::SharedMutex m_aMutex; + +public: + SvNumberFormatObj( SvNumberFormatsSupplierObj& rParent, sal_uLong nK, ::comphelper::SharedMutex _aMutex ); + virtual ~SvNumberFormatObj() override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > + SAL_CALL getPropertySetInfo( ) override; + virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, + const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( + const OUString& PropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const OUString& aPropertyName, + const css::uno::Reference< + css::beans::XPropertyChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const OUString& aPropertyName, + const css::uno::Reference< + css::beans::XPropertyChangeListener >& aListener ) override; + virtual void SAL_CALL addVetoableChangeListener( const OUString& PropertyName, + const css::uno::Reference< + css::beans::XVetoableChangeListener >& aListener ) override; + virtual void SAL_CALL removeVetoableChangeListener( const OUString& PropertyName, + const css::uno::Reference< + css::beans::XVetoableChangeListener >& aListener ) override; + + // XPropertyAccess + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL + getPropertyValues() override; + virtual void SAL_CALL setPropertyValues( const css::uno::Sequence< + css::beans::PropertyValue >& aProps ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; +}; + + +class SvNumberFormatSettingsObj : public cppu::WeakImplHelper< + css::beans::XPropertySet, + css::lang::XServiceInfo> +{ +private: + rtl::Reference + m_xSupplier; + mutable ::comphelper::SharedMutex m_aMutex; + +public: + SvNumberFormatSettingsObj( SvNumberFormatsSupplierObj& rParent, ::comphelper::SharedMutex _aMutex); + virtual ~SvNumberFormatSettingsObj() override; + + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > + SAL_CALL getPropertySetInfo( ) override; + virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, + const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( + const OUString& PropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const OUString& aPropertyName, + const css::uno::Reference< + css::beans::XPropertyChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const OUString& aPropertyName, + const css::uno::Reference< + css::beans::XPropertyChangeListener >& aListener ) override; + virtual void SAL_CALL addVetoableChangeListener( const OUString& PropertyName, + const css::uno::Reference< + css::beans::XVetoableChangeListener >& aListener ) override; + virtual void SAL_CALL removeVetoableChangeListener( const OUString& PropertyName, + const css::uno::Reference< + css::beans::XVetoableChangeListener >& aListener ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; +}; + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/numbers/numuno.cxx b/svl/source/numbers/numuno.cxx new file mode 100644 index 000000000..db633f405 --- /dev/null +++ b/svl/source/numbers/numuno.cxx @@ -0,0 +1,89 @@ +/* -*- 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 +#include "numfmuno.hxx" + +using namespace com::sun::star; + + +class SvNumFmtSuppl_Impl +{ +public: + SvNumberFormatter* pFormatter; + mutable ::comphelper::SharedMutex aMutex; + + explicit SvNumFmtSuppl_Impl(SvNumberFormatter* p) : + pFormatter(p) {} +}; + + +// Default ctor for getReflection +SvNumberFormatsSupplierObj::SvNumberFormatsSupplierObj() + : pImpl( new SvNumFmtSuppl_Impl(nullptr) ) +{ +} + +SvNumberFormatsSupplierObj::SvNumberFormatsSupplierObj(SvNumberFormatter* pForm) + : pImpl( new SvNumFmtSuppl_Impl(pForm) ) +{ +} + +SvNumberFormatsSupplierObj::~SvNumberFormatsSupplierObj() +{ +} + +::comphelper::SharedMutex& SvNumberFormatsSupplierObj::getSharedMutex() const +{ + return pImpl->aMutex; +} + +SvNumberFormatter* SvNumberFormatsSupplierObj::GetNumberFormatter() const +{ + return pImpl->pFormatter; +} + +void SvNumberFormatsSupplierObj::SetNumberFormatter(SvNumberFormatter* pNew) +{ + // The old Numberformatter has been retired, do not access it anymore! + pImpl->pFormatter = pNew; +} + +// XNumberFormatsSupplier + +uno::Reference SAL_CALL SvNumberFormatsSupplierObj::getNumberFormatSettings() +{ + ::osl::MutexGuard aGuard( pImpl->aMutex ); + + return new SvNumberFormatSettingsObj( *this, pImpl->aMutex ); +} + +uno::Reference SAL_CALL SvNumberFormatsSupplierObj::getNumberFormats() +{ + ::osl::MutexGuard aGuard( pImpl->aMutex ); + + return new SvNumberFormatsObj( *this, pImpl->aMutex ); +} + +// XUnoTunnel + +UNO3_GETIMPLEMENTATION_IMPL(SvNumberFormatsSupplierObj); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/numbers/supservs.cxx b/svl/source/numbers/supservs.cxx new file mode 100644 index 000000000..5a51158a2 --- /dev/null +++ b/svl/source/numbers/supservs.cxx @@ -0,0 +1,161 @@ +/* -*- 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 "supservs.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::util; +using namespace ::utl; + + +SvNumberFormatsSupplierServiceObject::SvNumberFormatsSupplierServiceObject(css::uno::Reference< css::uno::XComponentContext > _xORB) + :m_xORB(std::move(_xORB)) +{ +} + +SvNumberFormatsSupplierServiceObject::~SvNumberFormatsSupplierServiceObject() +{ +} + +Any SAL_CALL SvNumberFormatsSupplierServiceObject::queryAggregation( const Type& _rType ) +{ + Any aReturn = ::cppu::queryInterface(_rType, + static_cast< XInitialization* >(this), + static_cast< XServiceInfo* >(this) + ); + + if (!aReturn.hasValue()) + aReturn = SvNumberFormatsSupplierObj::queryAggregation(_rType); + + return aReturn; +} + +void SAL_CALL SvNumberFormatsSupplierServiceObject::initialize( const Sequence< Any >& _rArguments ) +{ + ::osl::MutexGuard aGuard( getSharedMutex() ); + + DBG_ASSERT(m_pOwnFormatter == nullptr, + "SvNumberFormatsSupplierServiceObject::initialize : already initialized !"); + // maybe you already called a method which needed the formatter + // you should use XMultiServiceFactory::createInstanceWithArguments to avoid that + if (m_pOwnFormatter) + { // !!! this is only an emergency handling, normally this should not occur !!! + m_pOwnFormatter.reset(); + SetNumberFormatter(m_pOwnFormatter.get()); + } + + Type aExpectedArgType = ::cppu::UnoType::get(); + LanguageType eNewFormatterLanguage = LANGUAGE_SYSTEM; + // the default + + for (const Any& rArg : _rArguments) + { + if (rArg.getValueType().equals(aExpectedArgType)) + { + css::lang::Locale aLocale; + rArg >>= aLocale; + eNewFormatterLanguage = LanguageTag::convertToLanguageType( aLocale, false); + } +#ifdef DBG_UTIL + else + { + OSL_FAIL("SvNumberFormatsSupplierServiceObject::initialize : unknown argument !"); + } +#endif + } + + m_pOwnFormatter.reset( new SvNumberFormatter( m_xORB, eNewFormatterLanguage) ); + m_pOwnFormatter->SetEvalDateFormat( NF_EVALDATEFORMAT_FORMAT_INTL ); + SetNumberFormatter(m_pOwnFormatter.get()); +} + +OUString SAL_CALL SvNumberFormatsSupplierServiceObject::getImplementationName( ) +{ + return "com.sun.star.uno.util.numbers.SvNumberFormatsSupplierServiceObject"; +} + +sal_Bool SAL_CALL SvNumberFormatsSupplierServiceObject::supportsService( const OUString& _rServiceName ) +{ + return cppu::supportsService(this, _rServiceName); +} + +Sequence< OUString > SAL_CALL SvNumberFormatsSupplierServiceObject::getSupportedServiceNames( ) +{ + return { "com.sun.star.util.NumberFormatsSupplier" }; +} + +Reference< XPropertySet > SAL_CALL SvNumberFormatsSupplierServiceObject::getNumberFormatSettings() +{ + ::osl::MutexGuard aGuard( getSharedMutex() ); + implEnsureFormatter(); + return SvNumberFormatsSupplierObj::getNumberFormatSettings(); +} + +Reference< XNumberFormats > SAL_CALL SvNumberFormatsSupplierServiceObject::getNumberFormats() +{ + ::osl::MutexGuard aGuard( getSharedMutex() ); + implEnsureFormatter(); + return SvNumberFormatsSupplierObj::getNumberFormats(); +} + +sal_Int64 SAL_CALL SvNumberFormatsSupplierServiceObject::getSomething( const Sequence< sal_Int8 >& aIdentifier ) +{ + sal_Int64 nReturn = SvNumberFormatsSupplierObj::getSomething( aIdentifier ); + if ( nReturn ) + // if somebody accesses internals then we should have the formatter + implEnsureFormatter(); + return nReturn; +} + +void SvNumberFormatsSupplierServiceObject::implEnsureFormatter() +{ + if (!m_pOwnFormatter) + { + // get the office's UI locale + SvtSysLocale aSysLocale; + css::lang::Locale aOfficeLocale = aSysLocale.GetLocaleData().getLanguageTag().getLocale(); + + // initialize with this locale + initialize({ Any(aOfficeLocale) }); + } +} + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_uno_util_numbers_SvNumberFormatsSupplierServiceObject_get_implementation(css::uno::XComponentContext* context, + css::uno::Sequence const &) +{ + return cppu::acquire(new SvNumberFormatsSupplierServiceObject(context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/numbers/supservs.hxx b/svl/source/numbers/supservs.hxx new file mode 100644 index 000000000..4ddf5751e --- /dev/null +++ b/svl/source/numbers/supservs.hxx @@ -0,0 +1,80 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVL_SOURCE_NUMBERS_SUPSERVS_HXX +#define INCLUDED_SVL_SOURCE_NUMBERS_SUPSERVS_HXX + +#include +#include +#include +#include +#include + +/** + * SvNumberFormatsSupplierServiceObject - a number formats supplier which + * - can be instantiated as a service + * - works with its own SvNumberFormatter instance + * - can be initialized (css::lang::XInitialization) + * with a specific language (i.e. css::lang::Locale) + */ +class SvNumberFormatsSupplierServiceObject final + :public SvNumberFormatsSupplierObj + ,public css::lang::XInitialization + ,public css::lang::XServiceInfo +{ + std::unique_ptr m_pOwnFormatter; + css::uno::Reference< css::uno::XComponentContext > m_xORB; + + void implEnsureFormatter(); + +public: + explicit SvNumberFormatsSupplierServiceObject(css::uno::Reference< css::uno::XComponentContext > _xORB); + virtual ~SvNumberFormatsSupplierServiceObject() override; + + // XInterface + virtual void SAL_CALL acquire() noexcept override { SvNumberFormatsSupplierObj::acquire(); } + virtual void SAL_CALL release() noexcept override { SvNumberFormatsSupplierObj::release(); } + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& _rType ) override + { return SvNumberFormatsSupplierObj::queryInterface(_rType); } + + // XAggregation + virtual css::uno::Any SAL_CALL queryAggregation( const css::uno::Type& _rType ) override; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // XNumberFormatsSupplier + virtual css::uno::Reference< css::beans::XPropertySet > SAL_CALL + getNumberFormatSettings() override; + virtual css::uno::Reference< css::util::XNumberFormats > SAL_CALL + getNumberFormats() override; + + // XUnoTunneler + virtual sal_Int64 SAL_CALL getSomething( const css::uno::Sequence< sal_Int8 >& aIdentifier ) override; +}; + + +#endif // INCLUDED_SVL_SOURCE_NUMBERS_SUPSERVS_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/numbers/zforfind.cxx b/svl/source/numbers/zforfind.cxx new file mode 100644 index 000000000..e3d8c752e --- /dev/null +++ b/svl/source/numbers/zforfind.cxx @@ -0,0 +1,4288 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "zforscan.hxx" +#include + +#include + +#include "zforfind.hxx" + +#ifndef DBG_UTIL +#define NF_TEST_CALENDAR 0 +#else +#define NF_TEST_CALENDAR 0 +#endif +#if NF_TEST_CALENDAR +#include +#include +#endif + + +const sal_uInt8 ImpSvNumberInputScan::nMatchedEndString = 0x01; +const sal_uInt8 ImpSvNumberInputScan::nMatchedMidString = 0x02; +const sal_uInt8 ImpSvNumberInputScan::nMatchedStartString = 0x04; +const sal_uInt8 ImpSvNumberInputScan::nMatchedVirgin = 0x08; +const sal_uInt8 ImpSvNumberInputScan::nMatchedUsedAsReturn = 0x10; + +/* It is not clear how we want timezones to be handled. Convert them to local + * time isn't wanted, as it isn't done in any other place and timezone + * information isn't stored anywhere. Ignoring them and pretending local time + * may be wrong too and might not be what the user expects. Keep the input as + * string so that no information is lost. + * Anyway, defining NF_RECOGNIZE_ISO8601_TIMEZONES to 1 would be the way how it + * would work, together with the nTimezonePos handling in GetTimeRef(). */ +#define NF_RECOGNIZE_ISO8601_TIMEZONES 0 + +const sal_Unicode cNoBreakSpace = 0xA0; +const sal_Unicode cNarrowNoBreakSpace = 0x202F; +const bool kDefaultEra = true; // Gregorian CE, positive year + +ImpSvNumberInputScan::ImpSvNumberInputScan( SvNumberFormatter* pFormatterP ) + : + bTextInitialized( false ), + bScanGenitiveMonths( false ), + bScanPartitiveMonths( false ), + eScannedType( SvNumFormatType::UNDEFINED ), + eSetType( SvNumFormatType::UNDEFINED ) +{ + pFormatter = pFormatterP; + pNullDate.reset( new Date(30,12,1899) ); + nYear2000 = SvNumberFormatter::GetYear2000Default(); + Reset(); + ChangeIntl(); +} + + +ImpSvNumberInputScan::~ImpSvNumberInputScan() +{ +} + + +void ImpSvNumberInputScan::Reset() +{ + mpFormat = nullptr; + nMonth = 0; + nMonthPos = 0; + nDayOfWeek = 0; + nTimePos = 0; + nSign = 0; + nESign = 0; + nDecPos = 0; + bNegCheck = false; + nStringsCnt = 0; + nNumericsCnt = 0; + nThousand = 0; + eScannedType = SvNumFormatType::UNDEFINED; + nAmPm = 0; + nPosThousandString = 0; + nLogical = 0; + mbEraCE = kDefaultEra; + nStringScanNumFor = 0; + nStringScanSign = 0; + nMatchedAllStrings = nMatchedVirgin; + nMayBeIso8601 = 0; + bIso8601Tsep = false; + nMayBeMonthDate = 0; + nAcceptedDatePattern = -2; + nDatePatternStart = 0; + nDatePatternNumbers = 0; + + for (sal_uInt32 i = 0; i < SV_MAX_COUNT_INPUT_STRINGS; i++) + { + IsNum[i] = false; + nNums[i] = 0; + } +} + +// native number transliteration if necessary +static void TransformInput( SvNumberFormatter const * pFormatter, OUString& rStr ) +{ + sal_Int32 nPos, nLen; + for ( nPos = 0, nLen = rStr.getLength(); nPos < nLen; ++nPos ) + { + if ( 256 <= rStr[ nPos ] && + pFormatter->GetCharClass()->isDigit( rStr, nPos ) ) + { + break; + } + } + if ( nPos < nLen ) + { + rStr = pFormatter->GetNatNum()->getNativeNumberString( rStr, + pFormatter->GetLanguageTag().getLocale(), 0 ); + } +} + + +/** + * Only simple unsigned floating point values without any error detection, + * decimal separator has to be '.' + */ +double ImpSvNumberInputScan::StringToDouble( std::u16string_view aStr, bool bForceFraction ) +{ + std::unique_ptr bufInHeap; + constexpr int bufOnStackSize = 256; + char bufOnStack[bufOnStackSize]; + char* buf = bufOnStack; + const sal_Int32 bufsize = aStr.size() + (bForceFraction ? 2 : 1); + if (bufsize > bufOnStackSize) + { + bufInHeap = std::make_unique(bufsize); + buf = bufInHeap.get(); + } + char* p = buf; + if (bForceFraction) + *p++ = '.'; + for (size_t nPos = 0; nPos < aStr.size(); ++nPos) + { + sal_Unicode c = aStr[nPos]; + if (c == '.' || (c >= '0' && c <= '9')) + *p++ = static_cast(c); + else + break; + } + *p = '\0'; + + return strtod_nolocale(buf, nullptr); +} + +namespace { + +/** + * Splits up the input into numbers and strings for further processing + * (by the Turing machine). + * + * Starting state = GetChar + * ---------------+-------------------+-----------------------------+--------------- + * Old State | Character read | Event | New state + * ---------------+-------------------+-----------------------------+--------------- + * GetChar | Number | Symbol = Character | GetValue + * | Else | Symbol = Character | GetString + * ---------------|-------------------+-----------------------------+--------------- + * GetValue | Number | Symbol = Symbol + Character | GetValue + * | Else | Dec(CharPos) | Stop + * ---------------+-------------------+-----------------------------+--------------- + * GetString | Number | Dec(CharPos) | Stop + * | Else | Symbol = Symbol + Character | GetString + * ---------------+-------------------+-----------------------------+--------------- + */ +enum ScanState // States of the Turing machine +{ + SsStop = 0, + SsStart = 1, + SsGetValue = 2, + SsGetString = 3 +}; + +} + +bool ImpSvNumberInputScan::NextNumberStringSymbol( const sal_Unicode*& pStr, + OUString& rSymbol ) +{ + bool isNumber = false; + sal_Unicode cToken; + ScanState eState = SsStart; + const sal_Unicode* pHere = pStr; + sal_Int32 nChars = 0; + + for (;;) + { + cToken = *pHere; + if (cToken == 0 || eState == SsStop) + break; + pHere++; + switch (eState) + { + case SsStart: + if ( rtl::isAsciiDigit( cToken ) ) + { + eState = SsGetValue; + isNumber = true; + } + else + { + eState = SsGetString; + } + nChars++; + break; + case SsGetValue: + if ( rtl::isAsciiDigit( cToken ) ) + { + nChars++; + } + else + { + eState = SsStop; + pHere--; + } + break; + case SsGetString: + if ( !rtl::isAsciiDigit( cToken ) ) + { + nChars++; + } + else + { + eState = SsStop; + pHere--; + } + break; + default: + break; + } // switch + } // while + + if ( nChars ) + { + rSymbol = OUString( pStr, nChars ); + } + else + { + rSymbol.clear(); + } + + pStr = pHere; + + return isNumber; +} + + +// FIXME: should be grouping; it is only used though in case nStringsCnt is +// near SV_MAX_COUNT_INPUT_STRINGS, in NumberStringDivision(). + +bool ImpSvNumberInputScan::SkipThousands( const sal_Unicode*& pStr, + OUString& rSymbol ) const +{ + bool res = false; + OUStringBuffer sBuff(rSymbol); + sal_Unicode cToken; + const OUString& rThSep = pFormatter->GetNumThousandSep(); + const sal_Unicode* pHere = pStr; + ScanState eState = SsStart; + sal_Int32 nCounter = 0; // counts 3 digits + + for (;;) + { + cToken = *pHere; + if (cToken == 0 || eState == SsStop) + break; + pHere++; + switch (eState) + { + case SsStart: + if ( StringPtrContains( rThSep, pHere-1, 0 ) ) + { + nCounter = 0; + eState = SsGetValue; + pHere += rThSep.getLength() - 1; + } + else + { + eState = SsStop; + pHere--; + } + break; + case SsGetValue: + if ( rtl::isAsciiDigit( cToken ) ) + { + sBuff.append(cToken); + nCounter++; + if (nCounter == 3) + { + eState = SsStart; + res = true; // .000 combination found + } + } + else + { + eState = SsStop; + pHere--; + } + break; + default: + break; + } // switch + } // while + + if (eState == SsGetValue) // break with less than 3 digits + { + if ( nCounter ) + { + sBuff.remove( sBuff.getLength() - nCounter, nCounter ); + } + pHere -= nCounter + rThSep.getLength(); // put back ThSep also + } + rSymbol = sBuff.makeStringAndClear(); + pStr = pHere; + + return res; +} + + +void ImpSvNumberInputScan::NumberStringDivision( const OUString& rString ) +{ + const sal_Unicode* pStr = rString.getStr(); + const sal_Unicode* const pEnd = pStr + rString.getLength(); + while ( pStr < pEnd && nStringsCnt < SV_MAX_COUNT_INPUT_STRINGS ) + { + if ( NextNumberStringSymbol( pStr, sStrArray[nStringsCnt] ) ) + { // Number + IsNum[nStringsCnt] = true; + nNums[nNumericsCnt] = nStringsCnt; + nNumericsCnt++; + if (nStringsCnt >= SV_MAX_COUNT_INPUT_STRINGS - 7 && + nPosThousandString == 0) // Only once + { + if ( SkipThousands( pStr, sStrArray[nStringsCnt] ) ) + { + nPosThousandString = nStringsCnt; + } + } + } + else + { + IsNum[nStringsCnt] = false; + } + nStringsCnt++; + } +} + + +/** + * Whether rString contains rWhat at nPos + */ +bool ImpSvNumberInputScan::StringContainsImpl( const OUString& rWhat, + const OUString& rString, sal_Int32 nPos ) +{ + if ( nPos + rWhat.getLength() <= rString.getLength() ) + { + return StringPtrContainsImpl( rWhat, rString.getStr(), nPos ); + } + return false; +} + + +/** + * Whether pString contains rWhat at nPos + */ +bool ImpSvNumberInputScan::StringPtrContainsImpl( const OUString& rWhat, + const sal_Unicode* pString, sal_Int32 nPos ) +{ + if ( rWhat.isEmpty() ) + { + return false; + } + const sal_Unicode* pWhat = rWhat.getStr(); + const sal_Unicode* const pEnd = pWhat + rWhat.getLength(); + const sal_Unicode* pStr = pString + nPos; + while ( pWhat < pEnd ) + { + if ( *pWhat != *pStr ) + { + return false; + } + pWhat++; + pStr++; + } + return true; +} + + +/** + * Whether rString contains word rWhat at nPos + */ +bool ImpSvNumberInputScan::StringContainsWord( const OUString& rWhat, + const OUString& rString, sal_Int32 nPos ) const +{ + if (rWhat.isEmpty() || rString.getLength() < nPos + rWhat.getLength()) + return false; + + if (StringPtrContainsImpl( rWhat, rString.getStr(), nPos)) + { + nPos += rWhat.getLength(); + if (nPos == rString.getLength()) + return true; // word at end of string + + /* TODO: we COULD invoke bells and whistles word break iterator to find + * the next boundary, but really ... this is called for date input, so + * how many languages do not separate the day and month names in some + * form? */ + + // Check simple ASCII first before invoking i18n or anything else. + const sal_Unicode c = rString[nPos]; + + // Common separating ASCII characters in date context. + switch (c) + { + case ' ': + case '-': + case '.': + case '/': + return true; + default: + ; // nothing + } + + if (rtl::isAsciiAlphanumeric( c )) + return false; // Alpha or numeric is not word gap. + + sal_Int32 nIndex = nPos; + rString.iterateCodePoints( &nIndex); + if (nPos+1 < nIndex) + return true; // Surrogate, assume these to be new words. + + const sal_Int32 nType = pFormatter->GetCharClass()->getCharacterType( rString, nPos); + using namespace ::com::sun::star::i18n; + + if ((nType & (KCharacterType::UPPER | KCharacterType::LOWER | KCharacterType::DIGIT)) != 0) + return false; // Alpha or numeric is not word gap. + + if (nType & KCharacterType::LETTER) + return true; // Letter other than alpha is new word. (Is it?) + + return true; // Catch all remaining as gap until we know better. + } + + return false; +} + + +/** + * Skips the supplied char + */ +inline bool ImpSvNumberInputScan::SkipChar( sal_Unicode c, const OUString& rString, + sal_Int32& nPos ) +{ + if ((nPos < rString.getLength()) && (rString[nPos] == c)) + { + nPos++; + return true; + } + return false; +} + + +/** + * Skips blanks + */ +inline bool ImpSvNumberInputScan::SkipBlanks( const OUString& rString, + sal_Int32& nPos ) +{ + sal_Int32 nHere = nPos; + if ( nPos < rString.getLength() ) + { + const sal_Unicode* p = rString.getStr() + nPos; + while ( *p == ' ' || *p == cNoBreakSpace || *p == cNarrowNoBreakSpace ) + { + nPos++; + p++; + } + } + return nHere < nPos; +} + + +/** + * jump over rWhat in rString at nPos + */ +inline bool ImpSvNumberInputScan::SkipString( const OUString& rWhat, + const OUString& rString, sal_Int32& nPos ) +{ + if ( StringContains( rWhat, rString, nPos ) ) + { + nPos = nPos + rWhat.getLength(); + return true; + } + return false; +} + + +/** + * Recognizes exactly ,111 in {3} and {3,2} or ,11 in {3,2} grouping + */ +inline bool ImpSvNumberInputScan::GetThousandSep( const OUString& rString, + sal_Int32& nPos, + sal_uInt16 nStringPos ) const +{ + const OUString& rSep = pFormatter->GetNumThousandSep(); + // Is it an ordinary space instead of a no-break space? + bool bSpaceBreak = (rSep[0] == cNoBreakSpace || rSep[0] == cNarrowNoBreakSpace) && + rString[0] == u' ' && + rSep.getLength() == 1 && rString.getLength() == 1; + if (!((rString == rSep || bSpaceBreak) && // nothing else + nStringPos < nStringsCnt - 1 && // safety first! + IsNum[ nStringPos + 1 ] )) // number follows + { + return false; // no? => out + } + + utl::DigitGroupingIterator aGrouping( pFormatter->GetLocaleData()->getDigitGrouping()); + // Match ,### in {3} or ,## in {3,2} + /* FIXME: this could be refined to match ,## in {3,2} only if ,##,## or + * ,##,### and to match ,### in {3,2} only if it's the last. However, + * currently there is no track kept where group separators occur. In {3,2} + * #,###,### and #,##,## would be valid input, which maybe isn't even bad + * for #,###,###. Other combinations such as #,###,## maybe not. */ + sal_Int32 nLen = sStrArray[ nStringPos + 1 ].getLength(); + if (nLen == aGrouping.get() || // with 3 (or so) digits + nLen == aGrouping.advance().get() || // or with 2 (or 3 or so) digits + nPosThousandString == nStringPos + 1 ) // or concatenated + { + nPos = nPos + rSep.getLength(); + return true; + } + return false; +} + + +/** + * Conversion of text to logical value + * "true" => 1: + * "false"=> -1: + * else => 0: + */ +short ImpSvNumberInputScan::GetLogical( std::u16string_view rString ) const +{ + short res; + + const ImpSvNumberformatScan* pFS = pFormatter->GetFormatScanner(); + if ( rString == pFS->GetTrueString() ) + { + res = 1; + } + else if ( rString == pFS->GetFalseString() ) + { + res = -1; + } + else + { + res = 0; + } + return res; +} + + +/** + * Converts a string containing a month name (JAN, January) at nPos into the + * month number (negative if abbreviated), returns 0 if nothing found + */ +short ImpSvNumberInputScan::GetMonth( const OUString& rString, sal_Int32& nPos ) +{ + short res = 0; // no month found + + if (rString.getLength() > nPos) // only if needed + { + if ( !bTextInitialized ) + { + InitText(); + } + sal_Int16 nMonths = pFormatter->GetCalendar()->getNumberOfMonthsInYear(); + for ( sal_Int16 i = 0; i < nMonths; i++ ) + { + if ( bScanGenitiveMonths && StringContainsWord( pUpperGenitiveMonthText[i], rString, nPos ) ) + { // genitive full names first + nPos = nPos + pUpperGenitiveMonthText[i].getLength(); + res = i + 1; + break; // for + } + else if ( bScanGenitiveMonths && StringContainsWord( pUpperGenitiveAbbrevMonthText[i], rString, nPos ) ) + { // genitive abbreviated + nPos = nPos + pUpperGenitiveAbbrevMonthText[i].getLength(); + res = sal::static_int_cast< short >(-(i+1)); // negative + break; // for + } + else if ( bScanPartitiveMonths && StringContainsWord( pUpperPartitiveMonthText[i], rString, nPos ) ) + { // partitive full names + nPos = nPos + pUpperPartitiveMonthText[i].getLength(); + res = i+1; + break; // for + } + else if ( bScanPartitiveMonths && StringContainsWord( pUpperPartitiveAbbrevMonthText[i], rString, nPos ) ) + { // partitive abbreviated + nPos = nPos + pUpperPartitiveAbbrevMonthText[i].getLength(); + res = sal::static_int_cast< short >(-(i+1)); // negative + break; // for + } + else if ( StringContainsWord( pUpperMonthText[i], rString, nPos ) ) + { // noun full names + nPos = nPos + pUpperMonthText[i].getLength(); + res = i+1; + break; // for + } + else if ( StringContainsWord( pUpperAbbrevMonthText[i], rString, nPos ) ) + { // noun abbreviated + nPos = nPos + pUpperAbbrevMonthText[i].getLength(); + res = sal::static_int_cast< short >(-(i+1)); // negative + break; // for + } + else if (i == 2 && pFormatter->GetLanguageTag().getLanguage() == "de") + { + if (pUpperAbbrevMonthText[i] == u"M\u00C4R" && StringContainsWord( "MRZ", rString, nPos)) + { // Accept MRZ for MÄR + nPos = nPos + 3; + res = sal::static_int_cast< short >(-(i+1)); // negative + break; // for + } + else if (pUpperAbbrevMonthText[i] == "MRZ" && StringContainsWord( u"M\u00C4R", rString, nPos)) + { // And vice versa, accept MÄR for MRZ + nPos = nPos + 3; + res = sal::static_int_cast< short >(-(i+1)); // negative + break; // for + } + } + else if (i == 8) + { + // This assumes the weirdness is applicable to all locales. + // It is the case for at least en-* and de-* locales. + if (pUpperAbbrevMonthText[i] == "SEPT" && StringContainsWord( "SEP", rString, nPos)) + { // #102136# The correct English form of month September abbreviated is + // SEPT, but almost every data contains SEP instead. + nPos = nPos + 3; + res = sal::static_int_cast< short >(-(i+1)); // negative + break; // for + } + else if (pUpperAbbrevMonthText[i] == "SEP" && StringContainsWord( "SEPT", rString, nPos)) + { // And vice versa, accept SEPT for SEP + nPos = nPos + 4; + res = sal::static_int_cast< short >(-(i+1)); // negative + break; // for + } + } + } + if (!res) + { + // Brutal hack for German locales that know "Januar" or "Jänner". + /* TODO: add alternative month names to locale data? if there are + * more languages... */ + const LanguageTag& rLanguageTag = pFormatter->GetLanguageTag(); + if (rLanguageTag.getLanguage() == "de") + { + if (rLanguageTag.getCountry() == "AT") + { + // Locale data has Jänner/Jän + assert(pUpperMonthText[0] == u"J\u00C4NNER"); + if (StringContainsWord( "JANUAR", rString, nPos)) + { + nPos += 6; + res = 1; + } + else if (StringContainsWord( "JAN", rString, nPos)) + { + nPos += 3; + res = -1; + } + } + else + { + // Locale data has Januar/Jan + assert(pUpperMonthText[0] == "JANUAR"); + if (StringContainsWord( u"J\u00C4NNER", rString, nPos)) + { + nPos += 6; + res = 1; + } + else if (StringContainsWord( u"J\u00C4N", rString, nPos)) + { + nPos += 3; + res = -1; + } + } + } + } + } + + return res; +} + + +/** + * Converts a string containing a DayOfWeek name (Mon, Monday) at nPos into the + * DayOfWeek number + 1 (negative if abbreviated), returns 0 if nothing found + */ +int ImpSvNumberInputScan::GetDayOfWeek( const OUString& rString, sal_Int32& nPos ) +{ + int res = 0; // no day found + + if (rString.getLength() > nPos) // only if needed + { + if ( !bTextInitialized ) + { + InitText(); + } + sal_Int16 nDays = pFormatter->GetCalendar()->getNumberOfDaysInWeek(); + for ( sal_Int16 i = 0; i < nDays; i++ ) + { + if ( StringContainsWord( pUpperDayText[i], rString, nPos ) ) + { // full names first + nPos = nPos + pUpperDayText[i].getLength(); + res = i + 1; + break; // for + } + if ( StringContainsWord( pUpperAbbrevDayText[i], rString, nPos ) ) + { // abbreviated + nPos = nPos + pUpperAbbrevDayText[i].getLength(); + res = -(i + 1); // negative + break; // for + } + } + } + + return res; +} + + +/** + * Reading a currency symbol + * '$' => true + * else => false + */ +bool ImpSvNumberInputScan::GetCurrency( const OUString& rString, sal_Int32& nPos ) +{ + if ( rString.getLength() > nPos ) + { + if ( !aUpperCurrSymbol.getLength() ) + { // If no format specified the currency of the currently active locale. + LanguageType eLang = (mpFormat ? mpFormat->GetLanguage() : + pFormatter->GetLocaleData()->getLanguageTag().getLanguageType()); + aUpperCurrSymbol = pFormatter->GetCharClass()->uppercase( + SvNumberFormatter::GetCurrencyEntry( eLang ).GetSymbol() ); + } + if ( StringContains( aUpperCurrSymbol, rString, nPos ) ) + { + nPos = nPos + aUpperCurrSymbol.getLength(); + return true; + } + if ( mpFormat ) + { + OUString aSymbol, aExtension; + if ( mpFormat->GetNewCurrencySymbol( aSymbol, aExtension ) ) + { + if ( aSymbol.getLength() <= rString.getLength() - nPos ) + { + aSymbol = pFormatter->GetCharClass()->uppercase(aSymbol); + if ( StringContains( aSymbol, rString, nPos ) ) + { + nPos = nPos + aSymbol.getLength(); + return true; + } + } + } + } + } + + return false; +} + + +/** + * Reading the time period specifier (AM/PM) for the 12 hour clock + * + * Returns: + * "AM" or "PM" => true + * else => false + * + * nAmPos: + * "AM" => 1 + * "PM" => -1 + * else => 0 +*/ +bool ImpSvNumberInputScan::GetTimeAmPm( const OUString& rString, sal_Int32& nPos ) +{ + + if ( rString.getLength() > nPos ) + { + const CharClass* pChr = pFormatter->GetCharClass(); + const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData(); + if ( StringContains( pChr->uppercase( pLoc->getTimeAM() ), rString, nPos ) ) + { + nAmPm = 1; + nPos = nPos + pLoc->getTimeAM().getLength(); + return true; + } + else if ( StringContains( pChr->uppercase( pLoc->getTimePM() ), rString, nPos ) ) + { + nAmPm = -1; + nPos = nPos + pLoc->getTimePM().getLength(); + return true; + } + } + + return false; +} + + +/** + * Read a decimal separator (',') + * ',' => true + * else => false + */ +inline bool ImpSvNumberInputScan::GetDecSep( const OUString& rString, sal_Int32& nPos ) const +{ + if ( rString.getLength() > nPos ) + { + const OUString& rSep = pFormatter->GetNumDecimalSep(); + if ( rString.match( rSep, nPos) ) + { + nPos = nPos + rSep.getLength(); + return true; + } + const OUString& rSepAlt = pFormatter->GetNumDecimalSepAlt(); + if ( !rSepAlt.isEmpty() && rString.match( rSepAlt, nPos) ) + { + nPos = nPos + rSepAlt.getLength(); + return true; + } + } + return false; +} + + +/** + * Reading a hundredth seconds separator + */ +inline bool ImpSvNumberInputScan::GetTime100SecSep( const OUString& rString, sal_Int32& nPos ) const +{ + if ( rString.getLength() > nPos ) + { + if (bIso8601Tsep) + { + // ISO 8601 specifies both '.' dot and ',' comma as fractional + // separator. + if (rString[nPos] == '.' || rString[nPos] == ',') + { + ++nPos; + return true; + } + } + // Even in an otherwise ISO 8601 string be lenient and accept the + // locale defined separator. + const OUString& rSep = pFormatter->GetLocaleData()->getTime100SecSep(); + if ( rString.match( rSep, nPos )) + { + nPos = nPos + rSep.getLength(); + return true; + } + } + return false; +} + + +/** + * Read a sign including brackets + * '+' => 1 + * '-' => -1 + * '(' => -1, bNegCheck = 1 + * else => 0 + */ +int ImpSvNumberInputScan::GetSign( const OUString& rString, sal_Int32& nPos ) +{ + if (rString.getLength() > nPos) + switch (rString[ nPos ]) + { + case '+': + nPos++; + return 1; + case '(': // '(' similar to '-' ?!? + bNegCheck = true; + [[fallthrough]]; + case '-': + nPos++; + return -1; + default: + break; + } + + return 0; +} + + +/** + * Read a sign with an exponent + * '+' => 1 + * '-' => -1 + * else => 0 + */ +short ImpSvNumberInputScan::GetESign( const OUString& rString, sal_Int32& nPos ) +{ + if (rString.getLength() > nPos) + { + switch (rString[nPos]) + { + case '+': + nPos++; + return 1; + case '-': + nPos++; + return -1; + default: + break; + } + } + return 0; +} + + +/** + * i counts string portions, j counts numbers thereof. + * It should had been called SkipNumber instead. + */ +inline bool ImpSvNumberInputScan::GetNextNumber( sal_uInt16& i, sal_uInt16& j ) const +{ + if ( i < nStringsCnt && IsNum[i] ) + { + j++; + i++; + return true; + } + return false; +} + + +bool ImpSvNumberInputScan::GetTimeRef( double& fOutNumber, + sal_uInt16 nIndex, // j-value of the first numeric time part of input, default 0 + sal_uInt16 nCnt, // count of numeric time parts + SvNumInputOptions eInputOptions + ) const +{ + bool bRet = true; + sal_uInt16 nHour; + sal_uInt16 nMinute = 0; + sal_uInt16 nSecond = 0; + double fSecond100 = 0.0; + sal_uInt16 nStartIndex = nIndex; + + if (nDecPos == 2 && (nCnt == 3 || nCnt == 2)) // 20:45.5 or 45.5 + { + nHour = 0; + } + else if (mpFormat && nDecPos == 0 && nCnt == 2 && mpFormat->IsMinuteSecondFormat()) + { + // Input on MM:SS format, instead of doing HH:MM:00 + nHour = 0; + } + else if (nIndex - nStartIndex < nCnt) + { + nHour = static_cast(sStrArray[nNums[nIndex++]].toInt32()); + } + else + { + nHour = 0; + bRet = false; + SAL_WARN( "svl.numbers", "ImpSvNumberInputScan::GetTimeRef: bad number index"); + } + + // 0:123 or 0:0:123 or 0:123:59 is valid + bool bAllowDuration = (nHour == 0 && !nAmPm); + + if (nAmPm && nHour > 12) // not a valid AM/PM clock time + { + bRet = false; + } + else if (nAmPm == -1 && nHour != 12) // PM + { + nHour += 12; + } + else if (nAmPm == 1 && nHour == 12) // 12 AM + { + nHour = 0; + } + + if (nDecPos == 2 && nCnt == 2) // 45.5 + { + nMinute = 0; + } + else if (nIndex - nStartIndex < nCnt) + { + nMinute = static_cast(sStrArray[nNums[nIndex++]].toInt32()); + if (!(eInputOptions & SvNumInputOptions::LAX_TIME) && !bAllowDuration + && nIndex > 1 && nMinute > 59) + bRet = false; // 1:60 or 1:123 is invalid, 123:1 or 0:123 is valid + if (bAllowDuration) + bAllowDuration = (nMinute == 0); + } + if (nIndex - nStartIndex < nCnt) + { + nSecond = static_cast(sStrArray[nNums[nIndex++]].toInt32()); + if (!(eInputOptions & SvNumInputOptions::LAX_TIME) && !bAllowDuration + && nIndex > 1 && nSecond > 59 && !(nHour == 23 && nMinute == 59 && nSecond == 60)) + bRet = false; // 1:60 or 1:123 or 1:1:123 is invalid, 123:1 or 123:1:1 or 0:0:123 is valid, or leap second + } + if (nIndex - nStartIndex < nCnt) + { + fSecond100 = StringToDouble( sStrArray[nNums[nIndex]], true ); + } + fOutNumber = (static_cast(nHour)*3600 + + static_cast(nMinute)*60 + + static_cast(nSecond) + + fSecond100)/86400.0; + return bRet; +} + + +sal_uInt16 ImpSvNumberInputScan::ImplGetDay( sal_uInt16 nIndex ) const +{ + sal_uInt16 nRes = 0; + + if (sStrArray[nNums[nIndex]].getLength() <= 2) + { + sal_uInt16 nNum = static_cast(sStrArray[nNums[nIndex]].toInt32()); + if (nNum <= 31) + { + nRes = nNum; + } + } + + return nRes; +} + + +sal_uInt16 ImpSvNumberInputScan::ImplGetMonth( sal_uInt16 nIndex ) const +{ + // Preset invalid month number + sal_uInt16 nRes = pFormatter->GetCalendar()->getNumberOfMonthsInYear(); + + if (sStrArray[nNums[nIndex]].getLength() <= 2) + { + sal_uInt16 nNum = static_cast(sStrArray[nNums[nIndex]].toInt32()); + if ( 0 < nNum && nNum <= nRes ) + { + nRes = nNum - 1; // zero based for CalendarFieldIndex::MONTH + } + } + + return nRes; +} + + +/** + * 30 -> 1930, 29 -> 2029, or 56 -> 1756, 55 -> 1855, ... + */ +sal_uInt16 ImpSvNumberInputScan::ImplGetYear( sal_uInt16 nIndex ) +{ + sal_uInt16 nYear = 0; + + sal_Int32 nLen = sStrArray[nNums[nIndex]].getLength(); + // 16-bit integer year width can have 5 digits, allow for one additional + // leading zero as convention. + if (nLen <= 6) + { + nYear = static_cast(sStrArray[nNums[nIndex]].toInt32()); + // A year in another, not Gregorian CE era is never expanded. + // A year < 100 entered with at least 3 digits with leading 0 is taken + // as is without expansion. + if (mbEraCE == kDefaultEra && nYear < 100 && nLen < 3) + { + nYear = SvNumberFormatter::ExpandTwoDigitYear( nYear, nYear2000 ); + } + } + + return nYear; +} + + +bool ImpSvNumberInputScan::MayBeIso8601() +{ + if (nMayBeIso8601 == 0) + { + nMayBeIso8601 = 1; + sal_Int32 nLen = ((nNumericsCnt >= 1 && nNums[0] < nStringsCnt) ? sStrArray[nNums[0]].getLength() : 0); + if (nLen) + { + sal_Int32 n; + if (nNumericsCnt >= 3 && nNums[2] < nStringsCnt && + sStrArray[nNums[0]+1] == "-" && // separator year-month + (n = sStrArray[nNums[1]].toInt32()) >= 1 && n <= 12 && // month + sStrArray[nNums[1]+1] == "-" && // separator month-day + (n = sStrArray[nNums[2]].toInt32()) >= 1 && n <= 31) // day + { + // Year (nNums[0]) value not checked, may be anything, but + // length (number of digits) is checked. + nMayBeIso8601 = (nLen >= 4 ? 4 : (nLen == 3 ? 3 : (nLen > 0 ? 2 : 1))); + } + } + } + return nMayBeIso8601 > 1; +} + + +bool ImpSvNumberInputScan::CanForceToIso8601( DateOrder eDateOrder ) +{ + int nCanForceToIso8601 = 0; + if (!MayBeIso8601()) + { + return false; + } + else if (nMayBeIso8601 >= 3) + { + return true; // at least 3 digits in year + } + else + { + if (eDateOrder == DateOrder::Invalid) + { + // As if any of the cases below can be applied, but only if a + // locale dependent date pattern was not matched. + if ((GetDatePatternNumbers() == nNumericsCnt) && IsDatePatternNumberOfType(0,'Y')) + return false; + eDateOrder = GetDateOrder(); + } + + nCanForceToIso8601 = 1; + } + + sal_Int32 n; + switch (eDateOrder) + { + case DateOrder::DMY: // "day" value out of range => ISO 8601 year + n = sStrArray[nNums[0]].toInt32(); + if (n < 1 || n > 31) + { + nCanForceToIso8601 = 2; + } + break; + case DateOrder::MDY: // "month" value out of range => ISO 8601 year + n = sStrArray[nNums[0]].toInt32(); + if (n < 1 || n > 12) + { + nCanForceToIso8601 = 2; + } + break; + case DateOrder::YMD: // always possible + nCanForceToIso8601 = 2; + break; + default: break; + } + return nCanForceToIso8601 > 1; +} + + +bool ImpSvNumberInputScan::IsAcceptableIso8601() +{ + if (mpFormat && (mpFormat->GetType() & SvNumFormatType::DATE)) + { + switch (pFormatter->GetEvalDateFormat()) + { + case NF_EVALDATEFORMAT_INTL: + return CanForceToIso8601( GetDateOrder()); + case NF_EVALDATEFORMAT_FORMAT: + return CanForceToIso8601( mpFormat->GetDateOrder()); + default: + return CanForceToIso8601( GetDateOrder()) || CanForceToIso8601( mpFormat->GetDateOrder()); + } + } + return CanForceToIso8601( GetDateOrder()); +} + + +bool ImpSvNumberInputScan::MayBeMonthDate() +{ + if (nMayBeMonthDate == 0) + { + nMayBeMonthDate = 1; + if (nNumericsCnt >= 2 && nNums[1] < nStringsCnt) + { + // "-Jan-" + const OUString& rM = sStrArray[ nNums[ 0 ] + 1 ]; + if (rM.getLength() >= 3 && rM[0] == '-' && rM[ rM.getLength() - 1] == '-') + { + // Check year length assuming at least 3 digits (including + // leading zero). Two digit years 1..31 are out of luck here + // and may be taken as day of month. + bool bYear1 = (sStrArray[nNums[0]].getLength() >= 3); + bool bYear2 = (sStrArray[nNums[1]].getLength() >= 3); + sal_Int32 n; + bool bDay1 = !bYear1; + if (bDay1) + { + n = sStrArray[nNums[0]].toInt32(); + bDay1 = n >= 1 && n <= 31; + } + bool bDay2 = !bYear2; + if (bDay2) + { + n = sStrArray[nNums[1]].toInt32(); + bDay2 = n >= 1 && n <= 31; + } + + if (bDay1 && !bDay2) + { + nMayBeMonthDate = 2; // dd-month-yy + } + else if (!bDay1 && bDay2) + { + nMayBeMonthDate = 3; // yy-month-dd + } + else if (bDay1 && bDay2) + { + // Ambiguous ##-MMM-## date, but some big vendor's database + // reports write this crap, assume this always to be + nMayBeMonthDate = 2; // dd-month-yy + } + } + } + } + return nMayBeMonthDate > 1; +} + + +/** If a string is a separator plus '-' minus sign preceding a 'Y' year in + a date pattern at position nPat. + */ +static bool lcl_IsSignedYearSep( const OUString& rStr, const OUString& rPat, sal_Int32 nPat ) +{ + bool bOk = false; + sal_Int32 nLen = rStr.getLength(); + if (nLen > 1 && rStr[nLen-1] == '-') + { + --nLen; + if (nPat + nLen < rPat.getLength() && rPat[nPat+nLen] == 'Y') + { + // Signed year is possible. + bOk = (rPat.indexOf( rStr.subView( 0, nLen), nPat) == nPat); + } + } + return bOk; +} + + +/** Length of separator usually is 1 but theoretically could be anything. */ +static sal_Int32 lcl_getPatternSeparatorLength( const OUString& rPat, sal_Int32 nPat ) +{ + sal_Int32 nSep = nPat; + sal_Unicode c; + while (nSep < rPat.getLength() && (c = rPat[nSep]) != 'D' && c != 'M' && c != 'Y') + ++nSep; + return nSep - nPat; +} + + +bool ImpSvNumberInputScan::IsAcceptedDatePattern( sal_uInt16 nStartPatternAt ) +{ + if (nAcceptedDatePattern >= -1) + { + return (nAcceptedDatePattern >= 0); + } + if (!nNumericsCnt) + { + nAcceptedDatePattern = -1; + } + else if (!sDateAcceptancePatterns.hasElements()) + { + // The current locale is the format's locale, if a format is present. + const NfEvalDateFormat eEDF = pFormatter->GetEvalDateFormat(); + if (!mpFormat || eEDF == NF_EVALDATEFORMAT_FORMAT || mpFormat->GetLanguage() == pFormatter->GetLanguage()) + { + sDateAcceptancePatterns = pFormatter->GetLocaleData()->getDateAcceptancePatterns(); + } + else + { + OnDemandLocaleDataWrapper& xLocaleData = pFormatter->GetOnDemandLocaleDataWrapper( + SvNumberFormatter::InputScannerPrivateAccess()); + const LanguageTag aSaveLocale( xLocaleData->getLanguageTag() ); + assert(mpFormat->GetLanguage() == aSaveLocale.getLanguageType()); // prerequisite + // Obtain formatter's locale's (e.g. system) patterns. + xLocaleData.changeLocale( LanguageTag( pFormatter->GetLanguage())); + const css::uno::Sequence aLocalePatterns( xLocaleData->getDateAcceptancePatterns()); + // Reset to format's locale. + xLocaleData.changeLocale( aSaveLocale); + // When concatenating don't care about duplicates, combining + // weeding those out reallocs yet another time and probably doesn't + // take less time than looping over two additional patterns below... + switch (eEDF) + { + case NF_EVALDATEFORMAT_FORMAT: + assert(!"shouldn't reach here"); + break; + case NF_EVALDATEFORMAT_INTL: + sDateAcceptancePatterns = aLocalePatterns; + break; + case NF_EVALDATEFORMAT_INTL_FORMAT: + sDateAcceptancePatterns = comphelper::concatSequences( + aLocalePatterns, + xLocaleData->getDateAcceptancePatterns()); + break; + case NF_EVALDATEFORMAT_FORMAT_INTL: + sDateAcceptancePatterns = comphelper::concatSequences( + xLocaleData->getDateAcceptancePatterns(), + aLocalePatterns); + break; + } + } + SAL_WARN_IF( !sDateAcceptancePatterns.hasElements(), "svl.numbers", "ImpSvNumberInputScan::IsAcceptedDatePattern: no date acceptance patterns"); + nAcceptedDatePattern = (sDateAcceptancePatterns.hasElements() ? -2 : -1); + } + + if (nAcceptedDatePattern == -1) + { + return false; + } + nDatePatternStart = nStartPatternAt; // remember start particle + + const sal_Int32 nMonthsInYear = pFormatter->GetCalendar()->getNumberOfMonthsInYear(); + + for (sal_Int32 nPattern=0; nPattern < sDateAcceptancePatterns.getLength(); ++nPattern) + { + const OUString& rPat = sDateAcceptancePatterns[nPattern]; + if (rPat.getLength() == 3) + { + // Ignore a pattern that would match numeric input with decimal + // separator. It may had been read from configuration or resulted + // from the locales' patterns concatenation above. + if ( rPat[1] == pFormatter->GetLocaleData()->getNumDecimalSep().toChar() + || rPat[1] == pFormatter->GetLocaleData()->getNumDecimalSepAlt().toChar()) + { + SAL_WARN("svl.numbers", "ignoring date acceptance pattern with decimal separator ambiguity: " << rPat); + continue; // for, next pattern + } + } + sal_uInt16 nNext = nDatePatternStart; + nDatePatternNumbers = 0; + bool bOk = true; + sal_Int32 nPat = 0; + for ( ; nPat < rPat.getLength() && bOk && nNext < nStringsCnt; ++nPat, ++nNext) + { + const sal_Unicode c = rPat[nPat]; + switch (c) + { + case 'Y': + case 'M': + case 'D': + bOk = IsNum[nNext]; + if (bOk && (c == 'M' || c == 'D')) + { + // Check the D and M cases for plausibility. This also + // prevents recognition of date instead of number with a + // numeric group input if date separator is identical to + // group separator, for example with D.M as a pattern and + // #.### as a group. + sal_Int32 nMaxLen, nMaxVal; + switch (c) + { + case 'M': + nMaxLen = 2; + nMaxVal = nMonthsInYear; + break; + case 'D': + nMaxLen = 2; + nMaxVal = 31; + break; + default: + // This merely exists against + // -Werror=maybe-uninitialized, which is nonsense + // after the (c == 'M' || c == 'D') check above, + // but ... + nMaxLen = 2; + nMaxVal = 31; + } + bOk = (sStrArray[nNext].getLength() <= nMaxLen); + if (bOk) + { + sal_Int32 nNum = sStrArray[nNext].toInt32(); + bOk = (1 <= nNum && nNum <= nMaxVal); + } + } + if (bOk) + ++nDatePatternNumbers; + break; + default: + bOk = !IsNum[nNext]; + if (bOk) + { + const sal_Int32 nSepLen = lcl_getPatternSeparatorLength( rPat, nPat); + // Non-numeric input must match separator exactly to be + // accepted as such. + const sal_Int32 nLen = sStrArray[nNext].getLength(); + bOk = (nLen == nSepLen && rPat.indexOf( sStrArray[nNext], nPat) == nPat); + if (bOk) + { + nPat += nLen - 1; + } + else if ((bOk = lcl_IsSignedYearSep( sStrArray[nNext], rPat, nPat))) + { + nPat += nLen - 2; + } + else if (nPat + nLen > rPat.getLength() && sStrArray[nNext][ nLen - 1 ] == ' ') + { + using namespace comphelper::string; + // Trailing blanks in input. + OUStringBuffer aBuf(sStrArray[nNext]); + aBuf.stripEnd(); + // Expand again in case of pattern "M. D. " and + // input "M. D. ", maybe fetched far, but... + padToLength(aBuf, rPat.getLength() - nPat, ' '); + bOk = (rPat.indexOf( aBuf, nPat) == nPat); + if (bOk) + { + nPat += aBuf.getLength() - 1; + } + } + } + break; + } + } + if (bOk) + { + // Check for trailing characters mismatch. + if (nNext < nStringsCnt) + { + // Pattern end but not input end. + // A trailing blank may be part of the current pattern input, + // if pattern is "D.M." and input is "D.M. hh:mm" last was + // ". ", or may be following the current pattern input, if + // pattern is "D.M" and input is "D.M hh:mm" last was "M". + sal_Int32 nPos = 0; + sal_uInt16 nCheck; + if (nPat > 0 && nNext > 0) + { + // nPat is one behind after the for loop. + sal_Int32 nPatCheck = nPat - 1; + switch (rPat[nPatCheck]) + { + case 'Y': + case 'M': + case 'D': + nCheck = nNext; + break; + default: + { + nCheck = nNext - 1; + // Advance position in input to match length of + // non-YMD (separator) characters in pattern. + sal_Unicode c; + do + { + ++nPos; + c = rPat[--nPatCheck]; + } while (c != 'Y' && c != 'M' && c != 'D' && nPatCheck > 0); + } + } + } + else + { + nCheck = nNext; + } + if (!IsNum[nCheck]) + { + // Trailing (or separating if time follows) blanks are ok. + // No blank and a following number is not. + const bool bBlanks = SkipBlanks( sStrArray[nCheck], nPos); + if (nPos == sStrArray[nCheck].getLength() && (bBlanks || !IsNum[nNext])) + { + nAcceptedDatePattern = nPattern; + return true; + } + } + } + else if (nPat == rPat.getLength()) + { + // Input end and pattern end => match. + nAcceptedDatePattern = nPattern; + return true; + } + // else Input end but not pattern end, no match. + } + } + nAcceptedDatePattern = -1; + return false; +} + + +bool ImpSvNumberInputScan::SkipDatePatternSeparator( sal_uInt16 nParticle, sal_Int32 & rPos, bool & rSignedYear ) +{ + // If not initialized yet start with first number, if any. + if (!IsAcceptedDatePattern( nNumericsCnt ? nNums[0] : 0 )) + { + return false; + } + if (nParticle < nDatePatternStart || nParticle >= nStringsCnt || IsNum[nParticle]) + { + return false; + } + sal_uInt16 nNext = nDatePatternStart; + const OUString& rPat = sDateAcceptancePatterns[nAcceptedDatePattern]; + for (sal_Int32 nPat = 0; nPat < rPat.getLength() && nNext < nStringsCnt; ++nPat, ++nNext) + { + switch (rPat[nPat]) + { + case 'Y': + case 'M': + case 'D': + break; + default: + if (nNext == nParticle) + { + const sal_Int32 nSepLen = lcl_getPatternSeparatorLength( rPat, nPat); + const sal_Int32 nLen = sStrArray[nNext].getLength(); + bool bOk = (nLen == nSepLen && rPat.indexOf( sStrArray[nNext], nPat) == nPat); + if (!bOk) + { + bOk = lcl_IsSignedYearSep( sStrArray[nNext], rPat, nPat); + if (bOk) + rSignedYear = true; + } + if (!bOk && (nPat + nLen > rPat.getLength() && sStrArray[nNext][ nLen - 1 ] == ' ')) + { + // The same ugly trailing blanks check as in + // IsAcceptedDatePattern(). + using namespace comphelper::string; + OUStringBuffer aBuf(sStrArray[nNext]); + aBuf.stripEnd(); + padToLength(aBuf, rPat.getLength() - nPat, ' '); + bOk = (rPat.indexOf(aBuf, nPat) == nPat); + } + if (bOk) + { + rPos = nLen; // yes, set, not add! + return true; + } + else + return false; + } + nPat += sStrArray[nNext].getLength() - 1; + break; + } + } + return false; +} + + +sal_uInt16 ImpSvNumberInputScan::GetDatePatternNumbers() +{ + // If not initialized yet start with first number, if any. + if (!IsAcceptedDatePattern( nNumericsCnt ? nNums[0] : 0 )) + { + return 0; + } + return nDatePatternNumbers; +} + + +bool ImpSvNumberInputScan::IsDatePatternNumberOfType( sal_uInt16 nNumber, sal_Unicode cType ) +{ + if (GetDatePatternNumbers() <= nNumber) + return false; + + sal_uInt16 nNum = 0; + const OUString& rPat = sDateAcceptancePatterns[nAcceptedDatePattern]; + for (sal_Int32 nPat = 0; nPat < rPat.getLength(); ++nPat) + { + switch (rPat[nPat]) + { + case 'Y': + case 'M': + case 'D': + if (nNum == nNumber) + return rPat[nPat] == cType; + ++nNum; + break; + } + } + return false; +} + + +sal_uInt32 ImpSvNumberInputScan::GetDatePatternOrder() +{ + // If not initialized yet start with first number, if any. + if (!IsAcceptedDatePattern( nNumericsCnt ? nNums[0] : 0 )) + { + return 0; + } + sal_uInt32 nOrder = 0; + const OUString& rPat = sDateAcceptancePatterns[nAcceptedDatePattern]; + for (sal_Int32 nPat = 0; nPat < rPat.getLength() && !(nOrder & 0xff0000); ++nPat) + { + switch (rPat[nPat]) + { + case 'Y': + case 'M': + case 'D': + nOrder = (nOrder << 8) | rPat[nPat]; + break; + } + } + return nOrder; +} + + +DateOrder ImpSvNumberInputScan::GetDateOrder( bool bFromFormatIfNoPattern ) +{ + sal_uInt32 nOrder = GetDatePatternOrder(); + if (!nOrder) + { + if (bFromFormatIfNoPattern && mpFormat) + return mpFormat->GetDateOrder(); + else + return pFormatter->GetLocaleData()->getDateOrder(); + } + switch ((nOrder & 0xff0000) >> 16) + { + case 'Y': + if ((((nOrder & 0xff00) >> 8) == 'M') && ((nOrder & 0xff) == 'D')) + { + return DateOrder::YMD; + } + break; + case 'M': + if ((((nOrder & 0xff00) >> 8) == 'D') && ((nOrder & 0xff) == 'Y')) + { + return DateOrder::MDY; + } + break; + case 'D': + if ((((nOrder & 0xff00) >> 8) == 'M') && ((nOrder & 0xff) == 'Y')) + { + return DateOrder::DMY; + } + break; + default: + case 0: + switch ((nOrder & 0xff00) >> 8) + { + case 'Y': + switch (nOrder & 0xff) + { + case 'M': + return DateOrder::YMD; + } + break; + case 'M': + switch (nOrder & 0xff) + { + case 'Y': + return DateOrder::DMY; + case 'D': + return DateOrder::MDY; + } + break; + case 'D': + switch (nOrder & 0xff) + { + case 'Y': + return DateOrder::MDY; + case 'M': + return DateOrder::DMY; + } + break; + default: + case 0: + switch (nOrder & 0xff) + { + case 'Y': + return DateOrder::YMD; + case 'M': + return DateOrder::MDY; + case 'D': + return DateOrder::DMY; + } + break; + } + } + SAL_WARN( "svl.numbers", "ImpSvNumberInputScan::GetDateOrder: undefined, falling back to locale's default"); + return pFormatter->GetLocaleData()->getDateOrder(); +} + +LongDateOrder ImpSvNumberInputScan::GetMiddleMonthLongDateOrder( bool bFormatTurn, + const LocaleDataWrapper* pLoc, + DateOrder eDateOrder ) +{ + if (MayBeMonthDate()) + return (nMayBeMonthDate == 2) ? LongDateOrder::DMY : LongDateOrder::YMD; + + LongDateOrder eLDO; + const sal_uInt32 nExactDateOrder = (bFormatTurn ? mpFormat->GetExactDateOrder() : 0); + if (!nExactDateOrder) + eLDO = pLoc->getLongDateOrder(); + else if ((((nExactDateOrder >> 16) & 0xff) == 'Y') && ((nExactDateOrder & 0xff) == 'D')) + eLDO = LongDateOrder::YMD; + else if ((((nExactDateOrder >> 16) & 0xff) == 'D') && ((nExactDateOrder & 0xff) == 'Y')) + eLDO = LongDateOrder::DMY; + else + eLDO = pLoc->getLongDateOrder(); + if (eLDO != LongDateOrder::YMD && eLDO != LongDateOrder::DMY) + { + switch (eDateOrder) + { + case DateOrder::YMD: + eLDO = LongDateOrder::YMD; + break; + case DateOrder::DMY: + eLDO = LongDateOrder::DMY; + break; + default: + ; // nothing, not a date + } + } + else if (eLDO == LongDateOrder::DMY && eDateOrder == DateOrder::YMD) + { + // Check possible order and maybe switch. + if (!ImplGetDay(0) && ImplGetDay(1)) + eLDO = LongDateOrder::YMD; + } + else if (eLDO == LongDateOrder::YMD && eDateOrder == DateOrder::DMY) + { + // Check possible order and maybe switch. + if (!ImplGetDay(1) && ImplGetDay(0)) + eLDO = LongDateOrder::DMY; + } + return eLDO; +} + +bool ImpSvNumberInputScan::GetDateRef( double& fDays, sal_uInt16& nCounter ) +{ + using namespace ::com::sun::star::i18n; + NfEvalDateFormat eEDF; + int nFormatOrder; + if ( mpFormat && (mpFormat->GetType() & SvNumFormatType::DATE) ) + { + eEDF = pFormatter->GetEvalDateFormat(); + switch ( eEDF ) + { + case NF_EVALDATEFORMAT_INTL : + case NF_EVALDATEFORMAT_FORMAT : + nFormatOrder = 1; // only one loop + break; + default: + nFormatOrder = 2; + if ( nMatchedAllStrings ) + { + eEDF = NF_EVALDATEFORMAT_FORMAT_INTL; + // we have a complete match, use it + } + } + } + else + { + eEDF = NF_EVALDATEFORMAT_INTL; + nFormatOrder = 1; + } + bool res = true; + + const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData(); + CalendarWrapper* pCal = pFormatter->GetCalendar(); + for ( int nTryOrder = 1; nTryOrder <= nFormatOrder; nTryOrder++ ) + { + pCal->setGregorianDateTime( Date( Date::SYSTEM ) ); // today + OUString aOrgCalendar; // empty => not changed yet + DateOrder DateFmt; + bool bFormatTurn; + switch ( eEDF ) + { + case NF_EVALDATEFORMAT_INTL : + bFormatTurn = false; + DateFmt = GetDateOrder(); + break; + case NF_EVALDATEFORMAT_FORMAT : + bFormatTurn = true; + DateFmt = mpFormat->GetDateOrder(); + break; + case NF_EVALDATEFORMAT_INTL_FORMAT : + if ( nTryOrder == 1 ) + { + bFormatTurn = false; + DateFmt = GetDateOrder(); + } + else + { + bFormatTurn = true; + DateFmt = mpFormat->GetDateOrder(); + } + break; + case NF_EVALDATEFORMAT_FORMAT_INTL : + if ( nTryOrder == 2 ) + { + bFormatTurn = false; + DateFmt = GetDateOrder(); + } + else + { + bFormatTurn = true; + // Even if the format pattern is to be preferred, the input may + // have matched a pattern of the current locale, which then + // again is to be preferred. Both date orders can be different + // so we need to obtain the actual match. For example ISO + // YYYY-MM-DD format vs locale's DD.MM.YY input. + // If no pattern was matched, obtain from format. + // Note that patterns may have been constructed from the + // format's locale and prepended to the current locale's + // patterns, it doesn't necessarily mean a current locale's + // pattern was matched, but may if the format's locale's + // patterns didn't match, which were tried first. + DateFmt = GetDateOrder(true); + } + break; + default: + SAL_WARN( "svl.numbers", "ImpSvNumberInputScan::GetDateRef: unknown NfEvalDateFormat" ); + DateFmt = DateOrder::YMD; + bFormatTurn = false; + } + if ( bFormatTurn ) + { +/* TODO: +We are currently not able to fully support a switch to another calendar during +input for the following reasons: +1. We do have a problem if both (locale's default and format's) calendars + define the same YMD order and use the same date separator, there is no way + to distinguish between them if the input results in valid calendar input for + both calendars. How to solve? Would NfEvalDateFormat be sufficient? Should + it always be set to NF_EVALDATEFORMAT_FORMAT_INTL and thus the format's + calendar be preferred? This could be confusing if a Calc cell was formatted + different to the locale's default and has no content yet, then the user has + no clue about the format or calendar being set. +2. In Calc cell edit mode a date is always displayed and edited using the + default edit format of the default calendar (normally being Gregorian). If + input was ambiguous due to issue #1 we'd need a mechanism to tell that a + date was edited and not newly entered. Not feasible. Otherwise we'd need a + mechanism to use a specific edit format with a specific calendar according + to the format set. +3. For some calendars like Japanese Gengou we'd need era input, which isn't + implemented at all. Though this is a rare and special case, forcing a + calendar dependent edit format as suggested in item #2 might require era + input, if it shouldn't result in a fallback to Gregorian calendar. +4. Last and least: the GetMonth() method currently only matches month names of + the default calendar. Alternating month names of the actual format's + calendar would have to be implemented. No problem. + +*/ +#ifdef THE_FUTURE + if ( mpFormat->IsOtherCalendar( nStringScanNumFor ) ) + { + mpFormat->SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + else + { + mpFormat->SwitchToSpecifiedCalendar( aOrgCalendar, fOrgDateTime, + nStringScanNumFor ); + } +#endif + } + + res = true; + nCounter = 0; + // For incomplete dates, always assume first day of month if not specified. + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, 1 ); + + switch (nNumericsCnt) // count of numbers in string + { + case 0: // none + if (nMonthPos) // only month (Jan) + { + pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 ); + } + else + { + res = false; + } + break; + + case 1: // only one number + nCounter = 1; + switch (nMonthPos) // where is the month + { + case 0: // not found + { + // If input matched a date pattern, use the pattern + // to determine if it is a day, month or year. The + // pattern should have only one single value then, + // 'D-', 'M-' or 'Y-'. If input did not match a + // pattern assume the usual day of current month. + sal_uInt32 nDateOrder = (bFormatTurn ? + mpFormat->GetExactDateOrder() : + GetDatePatternOrder()); + switch (nDateOrder) + { + case 'Y': + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) ); + break; + case 'M': + pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) ); + break; + case 'D': + default: + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) ); + break; + } + break; + } + case 1: // month at the beginning (Jan 01) + pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 ); + switch (DateFmt) + { + case DateOrder::MDY: + case DateOrder::YMD: + { + sal_uInt16 nDay = ImplGetDay(0); + sal_uInt16 nYear = ImplGetYear(0); + if (nDay == 0 || nDay > 32) + { + pCal->setValue( CalendarFieldIndex::YEAR, nYear); + } + else + { + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) ); + } + break; + } + case DateOrder::DMY: + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) ); + break; + default: + res = false; + break; + } + break; + case 3: // month at the end (10 Jan) + pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 ); + switch (DateFmt) + { + case DateOrder::DMY: + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) ); + break; + case DateOrder::YMD: + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) ); + break; + default: + res = false; + break; + } + break; + default: + res = false; + break; + } // switch (nMonthPos) + break; + + case 2: // 2 numbers + nCounter = 2; + switch (nMonthPos) // where is the month + { + case 0: // not found + { + sal_uInt32 nExactDateOrder = (bFormatTurn ? + mpFormat->GetExactDateOrder() : + GetDatePatternOrder()); + bool bIsExact = (0xff < nExactDateOrder && nExactDateOrder <= 0xffff); + if (!bIsExact && bFormatTurn && IsAcceptedDatePattern( nNums[0])) + { + // If input does not match format but pattern, use pattern + // instead, even if eEDF==NF_EVALDATEFORMAT_FORMAT_INTL. + // For example, format has "Y-M-D" and pattern is "D.M.", + // input with 2 numbers can't match format and 31.12. would + // lead to 1931-12-01 (fdo#54344) + nExactDateOrder = GetDatePatternOrder(); + bIsExact = (0xff < nExactDateOrder && nExactDateOrder <= 0xffff); + } + bool bHadExact; + if (bIsExact) + { + // formatted as date and exactly 2 parts + bHadExact = true; + switch ( (nExactDateOrder >> 8) & 0xff ) + { + case 'Y': + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) ); + break; + case 'M': + pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) ); + break; + case 'D': + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) ); + break; + default: + bHadExact = false; + } + switch ( nExactDateOrder & 0xff ) + { + case 'Y': + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) ); + break; + case 'M': + pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(1) ); + break; + case 'D': + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) ); + break; + default: + bHadExact = false; + } + SAL_WARN_IF( !bHadExact, "svl.numbers", "ImpSvNumberInputScan::GetDateRef: error in exact date order"); + } + else + { + bHadExact = false; + } + // If input matched against a date acceptance pattern + // do not attempt to mess around with guessing the + // order, either it matches or it doesn't. + if ((bFormatTurn || !bIsExact) && (!bHadExact || !pCal->isValid())) + { + if ( !bHadExact && nExactDateOrder ) + { + pCal->setGregorianDateTime( Date( Date::SYSTEM ) ); // reset today + } + switch (DateFmt) + { + case DateOrder::MDY: + // M D + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) ); + pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) ); + if ( !pCal->isValid() ) // 2nd try + { // M Y + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, 1 ); + pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) ); + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) ); + } + break; + case DateOrder::DMY: + // D M + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) ); + pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(1) ); + if ( !pCal->isValid() ) // 2nd try + { // M Y + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, 1 ); + pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) ); + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) ); + } + break; + case DateOrder::YMD: + // M D + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) ); + pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) ); + if ( !pCal->isValid() ) // 2nd try + { // Y M + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, 1 ); + pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(1) ); + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) ); + } + break; + default: + res = false; + break; + } + } + } + break; + case 1: // month at the beginning (Jan 01 01) + { + // The input is valid as MDY in almost any + // constellation, there is no date order (M)YD except if + // set in a format applied. + pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 ); + sal_uInt32 nExactDateOrder = (bFormatTurn ? mpFormat->GetExactDateOrder() : 0); + if ((((nExactDateOrder >> 8) & 0xff) == 'Y') && ((nExactDateOrder & 0xff) == 'D')) + { + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) ); + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) ); + } + else + { + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) ); + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) ); + } + break; + } + case 2: // month in the middle (10 Jan 94) + { + pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 ); + const LongDateOrder eLDO = GetMiddleMonthLongDateOrder( bFormatTurn, pLoc, DateFmt); + switch (eLDO) + { + case LongDateOrder::DMY: + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) ); + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) ); + break; + case LongDateOrder::YMD: + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) ); + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) ); + break; + default: + res = false; + break; + } + break; + } + default: // else, e.g. month at the end (94 10 Jan) + res = false; + break; + } // switch (nMonthPos) + break; + + default: // more than two numbers (31.12.94 8:23) (31.12. 8:23) + switch (nMonthPos) // where is the month + { + case 0: // not found + { + nCounter = 3; + if ( nTimePos > 1 ) + { // find first time number index (should only be 3 or 2 anyway) + for ( sal_uInt16 j = 0; j < nNumericsCnt; j++ ) + { + if ( nNums[j] == nTimePos - 2 ) + { + nCounter = j; + break; // for + } + } + } + // ISO 8601 yyyy-mm-dd forced recognition + DateOrder eDF = (CanForceToIso8601( DateFmt) ? DateOrder::YMD : DateFmt); + switch (eDF) + { + case DateOrder::MDY: + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) ); + pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) ); + if ( nCounter > 2 ) + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(2) ); + break; + case DateOrder::DMY: + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) ); + pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(1) ); + if ( nCounter > 2 ) + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(2) ); + break; + case DateOrder::YMD: + if ( nCounter > 2 ) + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(2) ); + pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(1) ); + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) ); + break; + default: + res = false; + break; + } + break; + } + case 1: // month at the beginning (Jan 01 01 8:23) + nCounter = 2; + switch (DateFmt) + { + case DateOrder::MDY: + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) ); + pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 ); + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) ); + break; + default: + res = false; + break; + } + break; + case 2: // month in the middle (10 Jan 94 8:23) + { + nCounter = 2; + pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 ); + const LongDateOrder eLDO = GetMiddleMonthLongDateOrder( bFormatTurn, pLoc, DateFmt); + switch (eLDO) + { + case LongDateOrder::DMY: + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) ); + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) ); + break; + case LongDateOrder::YMD: + pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) ); + pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) ); + break; + default: + res = false; + break; + } + break; + } + default: // else, e.g. month at the end (94 10 Jan 8:23) + nCounter = 2; + res = false; + break; + } // switch (nMonthPos) + break; + } // switch (nNumericsCnt) + + if (mbEraCE != kDefaultEra) + pCal->setValue( CalendarFieldIndex::ERA, mbEraCE ? 1 : 0); + + if ( res && pCal->isValid() ) + { + double fDiff = DateTime(*pNullDate) - pCal->getEpochStart(); + fDays = ::rtl::math::approxFloor( pCal->getLocalDateTime() ); + fDays -= fDiff; + nTryOrder = nFormatOrder; // break for + } + else + { + res = false; + } + if ( aOrgCalendar.getLength() ) + { + pCal->loadCalendar( aOrgCalendar, pLoc->getLanguageTag().getLocale() ); // restore calendar + } +#if NF_TEST_CALENDAR + { + using namespace ::com::sun::star; + struct entry { const char* lan; const char* cou; const char* cal; }; + const entry cals[] = { + { "en", "US", "gregorian" }, + { "ar", "TN", "hijri" }, + { "he", "IL", "jewish" }, + { "ja", "JP", "gengou" }, + { "ko", "KR", "hanja_yoil" }, + { "th", "TH", "buddhist" }, + { "zh", "TW", "ROC" }, + {0,0,0} + }; + lang::Locale aLocale; + bool bValid; + sal_Int16 nDay, nMyMonth, nYear, nHour, nMinute, nSecond; + sal_Int16 nDaySet, nMonthSet, nYearSet, nHourSet, nMinuteSet, nSecondSet; + sal_Int16 nZO, nDST1, nDST2, nDST, nZOmillis, nDST1millis, nDST2millis, nDSTmillis; + sal_Int32 nZoneInMillis, nDST1InMillis, nDST2InMillis; + uno::Reference< uno::XComponentContext > xContext = + ::comphelper::getProcessComponentContext(); + uno::Reference< i18n::XCalendar4 > xCal = i18n::LocaleCalendar2::create(xContext); + for ( const entry* p = cals; p->lan; ++p ) + { + aLocale.Language = OUString::createFromAscii( p->lan ); + aLocale.Country = OUString::createFromAscii( p->cou ); + xCal->loadCalendar( OUString::createFromAscii( p->cal ), + aLocale ); + double nDateTime = 0.0; // 1-Jan-1970 00:00:00 + nZO = xCal->getValue( i18n::CalendarFieldIndex::ZONE_OFFSET ); + nZOmillis = xCal->getValue( i18n::CalendarFieldIndex::ZONE_OFFSET_SECOND_MILLIS ); + nZoneInMillis = static_cast(nZO) * 60000 + + (nZO < 0 ? -1 : 1) * static_cast(nZOmillis); + nDST1 = xCal->getValue( i18n::CalendarFieldIndex::DST_OFFSET ); + nDST1millis = xCal->getValue( i18n::CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS ); + nDST1InMillis = static_cast(nDST1) * 60000 + + (nDST1 < 0 ? -1 : 1) * static_cast(nDST1millis); + nDateTime -= (double)(nZoneInMillis + nDST1InMillis) / 1000.0 / 60.0 / 60.0 / 24.0; + xCal->setDateTime( nDateTime ); + nDST2 = xCal->getValue( i18n::CalendarFieldIndex::DST_OFFSET ); + nDST2millis = xCal->getValue( i18n::CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS ); + nDST2InMillis = static_cast(nDST2) * 60000 + + (nDST2 < 0 ? -1 : 1) * static_cast(nDST2millis); + if ( nDST1InMillis != nDST2InMillis ) + { + nDateTime = 0.0 - (double)(nZoneInMillis + nDST2InMillis) / 1000.0 / 60.0 / 60.0 / 24.0; + xCal->setDateTime( nDateTime ); + } + nDaySet = xCal->getValue( i18n::CalendarFieldIndex::DAY_OF_MONTH ); + nMonthSet = xCal->getValue( i18n::CalendarFieldIndex::MONTH ); + nYearSet = xCal->getValue( i18n::CalendarFieldIndex::YEAR ); + nHourSet = xCal->getValue( i18n::CalendarFieldIndex::HOUR ); + nMinuteSet = xCal->getValue( i18n::CalendarFieldIndex::MINUTE ); + nSecondSet = xCal->getValue( i18n::CalendarFieldIndex::SECOND ); + nZO = xCal->getValue( i18n::CalendarFieldIndex::ZONE_OFFSET ); + nZOmillis = xCal->getValue( i18n::CalendarFieldIndex::ZONE_OFFSET_SECOND_MILLIS ); + nDST = xCal->getValue( i18n::CalendarFieldIndex::DST_OFFSET ); + nDSTmillis = xCal->getValue( i18n::CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS ); + xCal->setValue( i18n::CalendarFieldIndex::DAY_OF_MONTH, nDaySet ); + xCal->setValue( i18n::CalendarFieldIndex::MONTH, nMonthSet ); + xCal->setValue( i18n::CalendarFieldIndex::YEAR, nYearSet ); + xCal->setValue( i18n::CalendarFieldIndex::HOUR, nHourSet ); + xCal->setValue( i18n::CalendarFieldIndex::MINUTE, nMinuteSet ); + xCal->setValue( i18n::CalendarFieldIndex::SECOND, nSecondSet ); + bValid = xCal->isValid(); + nDay = xCal->getValue( i18n::CalendarFieldIndex::DAY_OF_MONTH ); + nMyMonth= xCal->getValue( i18n::CalendarFieldIndex::MONTH ); + nYear = xCal->getValue( i18n::CalendarFieldIndex::YEAR ); + nHour = xCal->getValue( i18n::CalendarFieldIndex::HOUR ); + nMinute = xCal->getValue( i18n::CalendarFieldIndex::MINUTE ); + nSecond = xCal->getValue( i18n::CalendarFieldIndex::SECOND ); + bValid = bValid && nDay == nDaySet && nMyMonth == nMonthSet && nYear == + nYearSet && nHour == nHourSet && nMinute == nMinuteSet && nSecond + == nSecondSet; + } + } +#endif // NF_TEST_CALENDAR + + } + + return res; +} + + +/** + * Analyze first string + * All gone => true + * else => false + */ +bool ImpSvNumberInputScan::ScanStartString( const OUString& rString ) +{ + sal_Int32 nPos = 0; + + // First of all, eat leading blanks + SkipBlanks(rString, nPos); + + // Yes, nMatchedAllStrings should know about the sign position + nSign = GetSign(rString, nPos); + if ( nSign ) // sign? + { + SkipBlanks(rString, nPos); + } + // #102371# match against format string only if start string is not a sign character + if ( nMatchedAllStrings && !(nSign && rString.getLength() == 1) ) + { + // Match against format in any case, so later on for a "x1-2-3" input + // we may distinguish between a xy-m-d (or similar) date and a x0-0-0 + // format. No sign detection here! + if ( ScanStringNumFor( rString, nPos, 0, true ) ) + { + nMatchedAllStrings |= nMatchedStartString; + } + else + { + nMatchedAllStrings = 0; + } + } + + // Bail out early for just a sign. + if (nSign && nPos == rString.getLength()) + return true; + + const sal_Int32 nStartBlanks = nPos; + if ( GetDecSep(rString, nPos) ) // decimal separator in start string + { + if (SkipBlanks(rString, nPos)) + nPos = nStartBlanks; // `. 2` not a decimal separator + else + nDecPos = 1; // leading decimal separator + } + else if ( GetCurrency(rString, nPos) ) // currency (DM 1)? + { + eScannedType = SvNumFormatType::CURRENCY; // !!! it IS currency !!! + SkipBlanks(rString, nPos); + if (nSign == 0) // no sign yet + { + nSign = GetSign(rString, nPos); + if ( nSign ) // DM -1 + { + SkipBlanks(rString, nPos); + } + } + if ( GetDecSep(rString, nPos) ) // decimal separator follows currency + { + if (SkipBlanks(rString, nPos)) + { + nPos = nStartBlanks; // `DM . 2` not a decimal separator + eScannedType = SvNumFormatType::UNDEFINED; // !!! it is NOT currency !!! + } + else + nDecPos = 1; // leading decimal separator + } + } + else + { + const sal_Int32 nMonthStart = nPos; + short nTempMonth = GetMonth(rString, nPos); + if (nTempMonth < 0) + { + // Short month and day names may be identical in some locales, e.g. + // "mar" for "martes" or "marzo" in Spanish. + // Do not let a month name immediately take precedence if a day + // name was meant instead. Assume that both could be valid, until + // encountered differently or the final evaluation in + // IsNumberFormat() checks, but continue with weighing the month + // name higher unless we have both day of week and month name here. + sal_Int32 nTempPos = nMonthStart; + nDayOfWeek = GetDayOfWeek( rString, nTempPos); + if (nDayOfWeek < 0) + { + SkipChar( '.', rString, nTempPos ); // abbreviated + SkipString( pFormatter->GetLocaleData()->getLongDateDayOfWeekSep(), rString, nTempPos ); + SkipBlanks( rString, nTempPos); + short nTempTempMonth = GetMonth( rString, nTempPos); + if (nTempTempMonth) + { + // Fall into the else branch below that handles both. + nTempMonth = 0; + nPos = nMonthStart; + nDayOfWeek = 0; + // Do not set nDayOfWeek hereafter, anywhere. + } + } + } + if ( nTempMonth ) // month (Jan 1)? + { + // Jan1 without separator is not a date, unless it is followed by a + // separator and a (year) number. + if (nPos < rString.getLength() || (nStringsCnt >= 4 && nNumericsCnt >= 2)) + { + eScannedType = SvNumFormatType::DATE; // !!! it IS a date !!! + nMonth = nTempMonth; + nMonthPos = 1; // month at the beginning + if ( nMonth < 0 ) + { + SkipChar( '.', rString, nPos ); // abbreviated + } + SkipBlanks(rString, nPos); + } + else + { + nPos = nMonthStart; // rewind month + } + } + else + { + int nTempDayOfWeek = GetDayOfWeek( rString, nPos ); + if ( nTempDayOfWeek ) + { + // day of week is just parsed away + eScannedType = SvNumFormatType::DATE; // !!! it IS a date !!! + if ( nPos < rString.getLength() ) + { + if ( nTempDayOfWeek < 0 ) + { + // abbreviated + if ( rString[ nPos ] == '.' ) + { + ++nPos; + } + } + else + { + // full long name + SkipBlanks(rString, nPos); + SkipString( pFormatter->GetLocaleData()->getLongDateDayOfWeekSep(), rString, nPos ); + } + SkipBlanks(rString, nPos); + nTempMonth = GetMonth(rString, nPos); + if ( nTempMonth ) // month (Jan 1)? + { + // Jan1 without separator is not a date, unless it is followed by a + // separator and a (year) number. + if (nPos < rString.getLength() || (nStringsCnt >= 4 && nNumericsCnt >= 2)) + { + nMonth = nTempMonth; + nMonthPos = 1; // month at the beginning + if ( nMonth < 0 ) + { + SkipChar( '.', rString, nPos ); // abbreviated + } + SkipBlanks(rString, nPos); + } + else + { + nPos = nMonthStart; // rewind month + } + } + } + if (!nMonth) + { + // Determine and remember following date pattern, if any. + IsAcceptedDatePattern( 1); + } + } + } + // Skip one trailing '-' or '/' character to recognize June-2007 + if (nMonth && nPos + 1 == rString.getLength()) + { + SkipChar('-', rString, nPos) || SkipChar('/', rString, nPos); + } + } + + if (nPos < rString.getLength()) // not everything consumed + { + // Does input StartString equal StartString of format? + // This time with sign detection! + if ( !ScanStringNumFor( rString, nPos, 0 ) ) + { + return MatchedReturn(); + } + } + + return true; +} + + +/** + * Analyze string in the middle + * All gone => true + * else => false + */ +bool ImpSvNumberInputScan::ScanMidString( const OUString& rString, sal_uInt16 nStringPos ) +{ + sal_Int32 nPos = 0; + SvNumFormatType eOldScannedType = eScannedType; + + if ( nMatchedAllStrings ) + { // Match against format in any case, so later on for a "1-2-3-4" input + // we may distinguish between a y-m-d (or similar) date and a 0-0-0-0 + // format. + if ( ScanStringNumFor( rString, 0, nStringPos ) ) + { + nMatchedAllStrings |= nMatchedMidString; + } + else + { + nMatchedAllStrings = 0; + } + } + + const sal_Int32 nStartBlanks = nPos; + const bool bBlanks = SkipBlanks(rString, nPos); + if (GetDecSep(rString, nPos)) // decimal separator? + { + if (nDecPos == 1 || nDecPos == 3) // .12.4 or 1.E2.1 + { + return MatchedReturn(); + } + else if (nDecPos == 2) // . dup: 12.4. + { + bool bSignedYear = false; + if (bDecSepInDateSeps || // . also date separator + SkipDatePatternSeparator( nStringPos, nPos, bSignedYear)) + { + if ( eScannedType != SvNumFormatType::UNDEFINED && + eScannedType != SvNumFormatType::DATE && + eScannedType != SvNumFormatType::DATETIME) // already another type + { + return MatchedReturn(); + } + if (eScannedType == SvNumFormatType::UNDEFINED) + { + eScannedType = SvNumFormatType::DATE; // !!! it IS a date + } + SkipBlanks(rString, nPos); + } + else + { + return MatchedReturn(); + } + } + else if (bBlanks) + { + // `1 .2` or `1 . 2` not a decimal separator, reset + nPos = nStartBlanks; + } + else if (SkipBlanks(rString, nPos)) + { + // `1. 2` not a decimal separator, reset + nPos = nStartBlanks; + } + else + { + nDecPos = 2; // . in mid string + } + } + else if ( (eScannedType & SvNumFormatType::TIME) && + GetTime100SecSep( rString, nPos ) ) + { // hundredth seconds separator + if ( nDecPos ) + { + return MatchedReturn(); + } + nDecPos = 2; // . in mid string + + // If this is exactly an ISO 8601 fractional seconds separator, bail + // out early to not get confused by later checks for group separator or + // other. + if (bIso8601Tsep && nPos == rString.getLength() && + eScannedType == SvNumFormatType::DATETIME && (rString == "." || rString == ",")) + return true; + + SkipBlanks(rString, nPos); + } + + if (SkipChar('/', rString, nPos)) // fraction? + { + if ( eScannedType != SvNumFormatType::UNDEFINED && // already another type + eScannedType != SvNumFormatType::DATE) // except date + { + return MatchedReturn(); // => jan/31/1994 + } + else if (eScannedType != SvNumFormatType::DATE && // analyzed no date until now + (eSetType == SvNumFormatType::FRACTION || // and preset was fraction + (nNumericsCnt == 3 && // or 3 numbers + (nStringPos == 3 || // and 4th string particle + (nStringPos == 4 && nSign)) && // or 5th if signed + sStrArray[nStringPos-2].indexOf('/') == -1))) // and not 23/11/1999 + // that was not accepted as date yet + { + SkipBlanks(rString, nPos); + if (nPos == rString.getLength()) + { + eScannedType = SvNumFormatType::FRACTION; // !!! it IS a fraction (so far) + if (eSetType == SvNumFormatType::FRACTION && + nNumericsCnt == 2 && + (nStringPos == 1 || // for 4/5 + (nStringPos == 2 && nSign))) // or signed -4/5 + { + return true; // don't fall into date trap + } + } + } + else + { + nPos--; // put '/' back + } + } + + if (GetThousandSep(rString, nPos, nStringPos)) // 1,000 + { + if ( eScannedType != SvNumFormatType::UNDEFINED && // already another type + eScannedType != SvNumFormatType::CURRENCY) // except currency + { + return MatchedReturn(); + } + nThousand++; + } + + const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData(); + bool bSignedYear = false; + bool bDate = SkipDatePatternSeparator( nStringPos, nPos, bSignedYear); // 12/31 31.12. 12/31/1999 31.12.1999 + if (!bDate) + { + const OUString& rDate = pFormatter->GetDateSep(); + SkipBlanks(rString, nPos); + bDate = SkipString( rDate, rString, nPos); // 10. 10- 10/ + } + if (!bDate && nStringPos == 1 && mpFormat && (mpFormat->GetType() & SvNumFormatType::DATE)) + { + // If a DMY format was given and a mid string starts with a literal + // ". " dot+space and could contain a following month name and ends + // with a space or LongDateMonthSeparator, like it's scanned in + // `14". AUG "18`, then it may be a date as well. Regardless whether + // defined such by the locale or not. + // This *could* check for presence of ". "MMM or ". "MMMM in the actual + // format code for further restriction to match only if present, but.. + + const sal_uInt32 nExactDateOrder = mpFormat->GetExactDateOrder(); + // Exactly DMY. + if (((nExactDateOrder & 0xff) == 'Y') && (((nExactDateOrder >> 8) & 0xff) == 'M') + && (((nExactDateOrder >> 16) & 0xff) == 'D')) + { + const sal_Int32 nTmpPos = nPos; + if (SkipChar('.', rString, nPos) && SkipBlanks(rString, nPos) && nPos + 2 < rString.getLength() + && (rString.endsWith(" ") || rString.endsWith( pLoc->getLongDateMonthSep()))) + bDate = true; + else + nPos = nTmpPos; + } + } + if (bDate || ((MayBeIso8601() || MayBeMonthDate()) && // 1999-12-31 31-Dec-1999 + SkipChar( '-', rString, nPos))) + { + if ( eScannedType != SvNumFormatType::UNDEFINED && // already another type + eScannedType != SvNumFormatType::DATE) // except date + { + return MatchedReturn(); + } + SkipBlanks(rString, nPos); + eScannedType = SvNumFormatType::DATE; // !!! it IS a date + short nTmpMonth = GetMonth(rString, nPos); // 10. Jan 94 + if (nMonth && nTmpMonth) // month dup + { + return MatchedReturn(); + } + if (nTmpMonth) + { + nMonth = nTmpMonth; + nMonthPos = 2; // month in the middle + if ( nMonth < 0 && SkipChar( '.', rString, nPos ) ) + ; // short month may be abbreviated Jan. + else if ( SkipChar( '-', rString, nPos ) ) + ; // #79632# recognize 17-Jan-2001 to be a date + // #99065# short and long month name + else + { + SkipString( pLoc->getLongDateMonthSep(), rString, nPos ); + } + SkipBlanks(rString, nPos); + } + if (bSignedYear) + { + if (mbEraCE != kDefaultEra) // signed year twice? + return MatchedReturn(); + + mbEraCE = false; // BCE + } + } + + const sal_Int32 nMonthStart = nPos; + short nTempMonth = GetMonth(rString, nPos); // month in the middle (10 Jan 94) + if (nTempMonth) + { + if (nMonth != 0) // month dup + { + return MatchedReturn(); + } + if ( eScannedType != SvNumFormatType::UNDEFINED && // already another type + eScannedType != SvNumFormatType::DATE) // except date + { + return MatchedReturn(); + } + if (nMonthStart > 0 && nPos < rString.getLength()) // 10Jan or Jan94 without separator are not dates + { + eScannedType = SvNumFormatType::DATE; // !!! it IS a date + nMonth = nTempMonth; + nMonthPos = 2; // month in the middle + if ( nMonth < 0 ) + { + SkipChar( '.', rString, nPos ); // abbreviated + } + SkipString( pLoc->getLongDateMonthSep(), rString, nPos ); + SkipBlanks(rString, nPos); + } + else + { + nPos = nMonthStart; // rewind month + } + } + + if ( SkipChar('E', rString, nPos) || // 10E, 10e, 10,Ee + SkipChar('e', rString, nPos) ) + { + if (eScannedType != SvNumFormatType::UNDEFINED) // already another type + { + return MatchedReturn(); + } + else + { + SkipBlanks(rString, nPos); + eScannedType = SvNumFormatType::SCIENTIFIC; // !!! it IS scientific + if ( nThousand+2 == nNumericsCnt && nDecPos == 2 ) // special case 1.E2 + { + nDecPos = 3; // 1,100.E2 1,100,100.E3 + } + } + nESign = GetESign(rString, nPos); // signed exponent? + SkipBlanks(rString, nPos); + } + + const OUString& rTime = pLoc->getTimeSep(); + if ( SkipString(rTime, rString, nPos) ) // time separator? + { + if (nDecPos) // already . => maybe error + { + if (bDecSepInDateSeps) // . also date sep + { + if ( eScannedType != SvNumFormatType::DATE && // already another type than date + eScannedType != SvNumFormatType::DATETIME) // or date time + { + return MatchedReturn(); + } + if (eScannedType == SvNumFormatType::DATE) + { + nDecPos = 0; // reset for time transition + } + } + else + { + return MatchedReturn(); + } + } + if ((eScannedType == SvNumFormatType::DATE || // already date type + eScannedType == SvNumFormatType::DATETIME) && // or date time + nNumericsCnt > 3) // and more than 3 numbers? (31.Dez.94 8:23) + { + SkipBlanks(rString, nPos); + eScannedType = SvNumFormatType::DATETIME; // !!! it IS date with time + } + else if ( eScannedType != SvNumFormatType::UNDEFINED && // already another type + eScannedType != SvNumFormatType::TIME) // except time + { + return MatchedReturn(); + } + else + { + SkipBlanks(rString, nPos); + eScannedType = SvNumFormatType::TIME; // !!! it IS a time + } + if ( !nTimePos ) + { + nTimePos = nStringPos + 1; + } + } + + if (nPos < rString.getLength()) + { + switch (eScannedType) + { + case SvNumFormatType::DATE: + if (nMonthPos == 1 && pLoc->getLongDateOrder() == LongDateOrder::MDY) + { + // #68232# recognize long date separators like ", " in "September 5, 1999" + if (SkipString( pLoc->getLongDateDaySep(), rString, nPos )) + { + SkipBlanks( rString, nPos ); + } + } + else if (nPos == 0 && rString.getLength() == 1 && MayBeIso8601()) + { + if ( (nStringPos == 5 && rString[0] == 'T') || + (nStringPos == 6 && rString[0] == 'T' && sStrArray[0] == "-")) + { + // ISO 8601 combined date and time, yyyy-mm-ddThh:mm or -yyyy-mm-ddThh:mm + ++nPos; + bIso8601Tsep = true; + } + else if (nStringPos == 7 && rString[0] == ':') + { + // ISO 8601 combined date and time, the time part; we reach + // here if the locale's separator is not ':' so it couldn't + // be detected above in the time block. + if (nNumericsCnt >= 5) + eScannedType = SvNumFormatType::DATETIME; + ++nPos; + } + } + break; + case SvNumFormatType::DATETIME: + if (nPos == 0 && rString.getLength() == 1 && MayBeIso8601()) + { + if (nStringPos == 9 && rString[0] == ':') + { + // ISO 8601 combined date and time, the time part continued. + ++nPos; + } + } +#if NF_RECOGNIZE_ISO8601_TIMEZONES + else if (nPos == 0 && rString.getLength() == 1 && nStringPos >= 9 && MayBeIso8601()) + { + // ISO 8601 timezone offset + switch (rString[ 0 ]) + { + case '+': + case '-': + if (nStringPos == nStringsCnt - 2 || + nStringPos == nStringsCnt - 4) + { + ++nPos; // yyyy-mm-ddThh:mm[:ss]+xx[[:]yy] + // nTimezonePos needed for GetTimeRef() + if (!nTimezonePos) + { + nTimezonePos = nStringPos + 1; + } + } + break; + case ':': + if (nTimezonePos && nStringPos >= 11 && + nStringPos == nStringsCnt - 2) + { + ++nPos; // yyyy-mm-ddThh:mm[:ss]+xx:yy + } + break; + } + } +#endif + break; + default: break; + } + } + + if (nPos < rString.getLength()) // not everything consumed? + { + if ( nMatchedAllStrings & ~nMatchedVirgin ) + { + eScannedType = eOldScannedType; + } + else + { + return false; + } + } + + return true; +} + + +/** + * Analyze the end + * All gone => true + * else => false + */ +bool ImpSvNumberInputScan::ScanEndString( const OUString& rString ) +{ + sal_Int32 nPos = 0; + + if ( nMatchedAllStrings ) + { // Match against format in any case, so later on for a "1-2-3-4" input + // we may distinguish between a y-m-d (or similar) date and a 0-0-0-0 + // format. + if ( ScanStringNumFor( rString, 0, 0xFFFF ) ) + { + nMatchedAllStrings |= nMatchedEndString; + } + else + { + nMatchedAllStrings = 0; + } + } + + const sal_Int32 nStartBlanks = nPos; + const bool bBlanks = SkipBlanks(rString, nPos); + if (GetDecSep(rString, nPos)) // decimal separator? + { + if (nDecPos == 1 || nDecPos == 3) // .12.4 or 12.E4. + { + return MatchedReturn(); + } + else if (nDecPos == 2) // . dup: 12.4. + { + bool bSignedYear = false; + if (bDecSepInDateSeps || // . also date separator + SkipDatePatternSeparator( nStringsCnt-1, nPos, bSignedYear)) + { + if ( eScannedType != SvNumFormatType::UNDEFINED && + eScannedType != SvNumFormatType::DATE && + eScannedType != SvNumFormatType::DATETIME) // already another type + { + return MatchedReturn(); + } + if (eScannedType == SvNumFormatType::UNDEFINED) + { + eScannedType = SvNumFormatType::DATE; // !!! it IS a date + } + SkipBlanks(rString, nPos); + } + else + { + return MatchedReturn(); + } + } + else if (bBlanks) + { + // not a decimal separator, reset + nPos = nStartBlanks; + } + else + { + nDecPos = 3; // . in end string + SkipBlanks(rString, nPos); + } + } + + bool bSignDetectedHere = false; + if ( nSign == 0 && // conflict - not signed + eScannedType != SvNumFormatType::DATE) // and not date + //!? catch time too? + { // not signed yet + nSign = GetSign(rString, nPos); // 1- DM + if (bNegCheck) // '(' as sign + { + return MatchedReturn(); + } + if (nSign) + { + bSignDetectedHere = true; + } + } + + SkipBlanks(rString, nPos); + if (bNegCheck && SkipChar(')', rString, nPos)) // skip ')' if appropriate + { + bNegCheck = false; + SkipBlanks(rString, nPos); + } + + if ( GetCurrency(rString, nPos) ) // currency symbol? + { + if (eScannedType != SvNumFormatType::UNDEFINED) // currency dup + { + return MatchedReturn(); + } + else + { + SkipBlanks(rString, nPos); + eScannedType = SvNumFormatType::CURRENCY; + } // behind currency a '-' is allowed + if (nSign == 0) // not signed yet + { + nSign = GetSign(rString, nPos); // DM - + SkipBlanks(rString, nPos); + if (bNegCheck) // 3 DM ( + { + return MatchedReturn(); + } + } + if ( bNegCheck && eScannedType == SvNumFormatType::CURRENCY && + SkipChar(')', rString, nPos) ) + { + bNegCheck = false; // ')' skipped + SkipBlanks(rString, nPos); // only if currency + } + } + + if ( SkipChar('%', rString, nPos) ) // 1% + { + if (eScannedType != SvNumFormatType::UNDEFINED) // already another type + { + return MatchedReturn(); + } + SkipBlanks(rString, nPos); + eScannedType = SvNumFormatType::PERCENT; + } + + const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData(); + const OUString& rTime = pLoc->getTimeSep(); + if ( SkipString(rTime, rString, nPos) ) // 10: + { + if (nDecPos) // already , => error + { + return MatchedReturn(); + } + if (eScannedType == SvNumFormatType::DATE && nNumericsCnt > 2) // 31.Dez.94 8: + { + SkipBlanks(rString, nPos); + eScannedType = SvNumFormatType::DATETIME; + } + else if (eScannedType != SvNumFormatType::UNDEFINED && + eScannedType != SvNumFormatType::TIME) // already another type + { + return MatchedReturn(); + } + else + { + SkipBlanks(rString, nPos); + eScannedType = SvNumFormatType::TIME; + } + if ( !nTimePos ) + { + nTimePos = nStringsCnt; + } + } + + bool bSignedYear = false; + bool bDate = SkipDatePatternSeparator( nStringsCnt-1, nPos, bSignedYear); // 12/31 31.12. 12/31/1999 31.12.1999 + if (!bDate) + { + const OUString& rDate = pFormatter->GetDateSep(); + bDate = SkipString( rDate, rString, nPos); // 10. 10- 10/ + } + if (bDate && bSignDetectedHere) + { + nSign = 0; // 'D-' takes precedence over signed date + } + if (bDate || ((MayBeIso8601() || MayBeMonthDate()) + && SkipChar( '-', rString, nPos))) + { + if (eScannedType != SvNumFormatType::UNDEFINED && + eScannedType != SvNumFormatType::DATE) // already another type + { + return MatchedReturn(); + } + else + { + SkipBlanks(rString, nPos); + eScannedType = SvNumFormatType::DATE; + } + short nTmpMonth = GetMonth(rString, nPos); // 10. Jan + if (nMonth && nTmpMonth) // month dup + { + return MatchedReturn(); + } + if (nTmpMonth) + { + nMonth = nTmpMonth; + nMonthPos = 3; // month at end + if ( nMonth < 0 ) + { + SkipChar( '.', rString, nPos ); // abbreviated + } + SkipBlanks(rString, nPos); + } + } + + const sal_Int32 nMonthStart = nPos; + short nTempMonth = GetMonth(rString, nPos); // 10 Jan + if (nTempMonth) + { + if (nMonth) // month dup + { + return MatchedReturn(); + } + if (eScannedType != SvNumFormatType::UNDEFINED && + eScannedType != SvNumFormatType::DATE) // already another type + { + return MatchedReturn(); + } + if (nMonthStart > 0) // 10Jan without separator is not a date + { + eScannedType = SvNumFormatType::DATE; + nMonth = nTempMonth; + nMonthPos = 3; // month at end + if ( nMonth < 0 ) + { + SkipChar( '.', rString, nPos ); // abbreviated + } + SkipBlanks(rString, nPos); + } + else + { + nPos = nMonthStart; // rewind month + } + } + + sal_Int32 nOrigPos = nPos; + if (GetTimeAmPm(rString, nPos)) + { + if (eScannedType != SvNumFormatType::UNDEFINED && + eScannedType != SvNumFormatType::TIME && + eScannedType != SvNumFormatType::DATETIME) // already another type + { + return MatchedReturn(); + } + else + { + // If not already scanned as time, 6.78am does not result in 6 + // seconds and 78 hundredths in the morning. Keep as suffix. + if (eScannedType != SvNumFormatType::TIME && nDecPos == 2 && nNumericsCnt == 2) + { + nPos = nOrigPos; // rewind am/pm + } + else + { + SkipBlanks(rString, nPos); + if ( eScannedType != SvNumFormatType::DATETIME ) + { + eScannedType = SvNumFormatType::TIME; + } + } + } + } + + if ( bNegCheck && SkipChar(')', rString, nPos) ) + { + if (eScannedType == SvNumFormatType::CURRENCY) // only if currency + { + bNegCheck = false; // skip ')' + SkipBlanks(rString, nPos); + } + else + { + return MatchedReturn(); + } + } + + if ( nPos < rString.getLength() && + (eScannedType == SvNumFormatType::DATE || + eScannedType == SvNumFormatType::DATETIME) ) + { + // day of week is just parsed away + sal_Int32 nOldPos = nPos; + const OUString& rSep = pFormatter->GetLocaleData()->getLongDateDayOfWeekSep(); + if ( StringContains( rSep, rString, nPos ) ) + { + nPos = nPos + rSep.getLength(); + SkipBlanks(rString, nPos); + } + int nTempDayOfWeek = GetDayOfWeek( rString, nPos ); + if ( nTempDayOfWeek ) + { + if ( nPos < rString.getLength() ) + { + if ( nTempDayOfWeek < 0 ) + { // short + if ( rString[ nPos ] == '.' ) + { + ++nPos; + } + } + SkipBlanks(rString, nPos); + } + } + else + { + nPos = nOldPos; + } + } + +#if NF_RECOGNIZE_ISO8601_TIMEZONES + if (nPos == 0 && eScannedType == SvNumFormatType::DATETIME && + rString.getLength() == 1 && rString[ 0 ] == 'Z' && MayBeIso8601()) + { + // ISO 8601 timezone UTC yyyy-mm-ddThh:mmZ + ++nPos; + } +#endif + + if (nPos < rString.getLength()) // everything consumed? + { + // does input EndString equal EndString in Format? + if ( !ScanStringNumFor( rString, nPos, 0xFFFF ) ) + { + return false; + } + } + + return true; +} + + +bool ImpSvNumberInputScan::ScanStringNumFor( const OUString& rString, // String to scan + sal_Int32 nPos, // Position until which was consumed + sal_uInt16 nString, // Substring of format, 0xFFFF => last + bool bDontDetectNegation) // Suppress sign detection +{ + if ( !mpFormat ) + { + return false; + } + const ::utl::TransliterationWrapper* pTransliteration = pFormatter->GetTransliteration(); + const OUString* pStr; + OUString aString( rString ); + bool bFound = false; + bool bFirst = true; + bool bContinue = true; + sal_uInt16 nSub; + do + { + // Don't try "lower" subformats ff the very first match was the second + // or third subformat. + nSub = nStringScanNumFor; + do + { // Step through subformats, first positive, then negative, then + // other, but not the last (text) subformat. + pStr = mpFormat->GetNumForString( nSub, nString, true ); + if ( pStr && pTransliteration->isEqual( aString, *pStr ) ) + { + bFound = true; + bContinue = false; + } + else if ( nSub < 2 ) + { + ++nSub; + } + else + { + bContinue = false; + } + } + while ( bContinue ); + if ( !bFound && bFirst && nPos ) + { + // try remaining substring + bFirst = false; + aString = aString.copy(nPos); + bContinue = true; + } + } + while ( bContinue ); + + if ( !bFound ) + { + if ( !bDontDetectNegation && (nString == 0) && + !bFirst && (nSign < 0) && mpFormat->IsSecondSubformatRealNegative() ) + { + // simply negated twice? --1 + aString = aString.replaceAll(" ", ""); + if ( (aString.getLength() == 1) && (aString[0] == '-') ) + { + bFound = true; + nStringScanSign = -1; + nSub = 0; //! not 1 + } + } + if ( !bFound ) + { + return false; + } + } + else if ( !bDontDetectNegation && (nSub == 1) && + mpFormat->IsSecondSubformatRealNegative() ) + { + // negative + if ( nStringScanSign < 0 ) + { + if ( (nSign < 0) && (nStringScanNumFor != 1) ) + { + nStringScanSign = 1; // triple negated --1 yyy + } + } + else if ( nStringScanSign == 0 ) + { + if ( nSign < 0 ) + { // nSign and nStringScanSign will be combined later, + // flip sign if doubly negated + if ( (nString == 0) && !bFirst && + SvNumberformat::HasStringNegativeSign( aString ) ) + { + nStringScanSign = -1; // direct double negation + } + else if ( mpFormat->IsNegativeWithoutSign() ) + { + nStringScanSign = -1; // indirect double negation + } + } + else + { + nStringScanSign = -1; + } + } + else // > 0 + { + nStringScanSign = -1; + } + } + nStringScanNumFor = nSub; + return true; +} + + +/** + * Recognizes types of number, exponential, fraction, percent, currency, date, time. + * Else text => return false + */ +bool ImpSvNumberInputScan::IsNumberFormatMain( const OUString& rString, // string to be analyzed + const SvNumberformat* pFormat ) // maybe number format set to match against +{ + Reset(); + mpFormat = pFormat; + NumberStringDivision( rString ); // breakdown into strings and numbers + if (nStringsCnt >= SV_MAX_COUNT_INPUT_STRINGS) // too many elements + { + return false; // Njet, Nope, ... + } + if (nNumericsCnt == 0) // no number in input + { + if ( nStringsCnt > 0 ) + { + // Here we may change the original, we don't need it anymore. + // This saves copies and ToUpper() in GetLogical() and is faster. + sStrArray[0] = comphelper::string::strip(sStrArray[0], ' '); + OUString& rStrArray = sStrArray[0]; + nLogical = GetLogical( rStrArray ); + if ( nLogical ) + { + eScannedType = SvNumFormatType::LOGICAL; // !!! it's a BOOLEAN + nMatchedAllStrings &= ~nMatchedVirgin; + return true; + } + else + { + return false; // simple text + } + } + else + { + return false; // simple text + } + } + + sal_uInt16 i = 0; // mark any symbol + sal_uInt16 j = 0; // mark only numbers + + switch ( nNumericsCnt ) + { + case 1 : // Exactly 1 number in input + // nStringsCnt >= 1 + if (GetNextNumber(i,j)) // i=1,0 + { // Number at start + if (eSetType == SvNumFormatType::FRACTION) // Fraction 1 = 1/1 + { + if (i >= nStringsCnt || // no end string nor decimal separator + pFormatter->IsDecimalSep( sStrArray[i])) + { + eScannedType = SvNumFormatType::FRACTION; + nMatchedAllStrings &= ~nMatchedVirgin; + return true; + } + } + } + else + { // Analyze start string + if (!ScanStartString( sStrArray[i] )) // i=0 + { + return false; // already an error + } + i++; // next symbol, i=1 + } + GetNextNumber(i,j); // i=1,2 + if (eSetType == SvNumFormatType::FRACTION) // Fraction -1 = -1/1 + { + if (nSign && !bNegCheck && // Sign +, - + eScannedType == SvNumFormatType::UNDEFINED && // not date or currency + nDecPos == 0 && // no previous decimal separator + (i >= nStringsCnt || // no end string nor decimal separator + pFormatter->IsDecimalSep( sStrArray[i])) + ) + { + eScannedType = SvNumFormatType::FRACTION; + nMatchedAllStrings &= ~nMatchedVirgin; + return true; + } + } + if (i < nStringsCnt && !ScanEndString( sStrArray[i] )) + { + return false; + } + break; + case 2 : // Exactly 2 numbers in input + // nStringsCnt >= 3 + if (!GetNextNumber(i,j)) // i=1,0 + { // Analyze start string + if (!ScanStartString( sStrArray[i] )) + return false; // already an error + i++; // i=1 + } + GetNextNumber(i,j); // i=1,2 + if ( !ScanMidString( sStrArray[i], i ) ) + { + return false; + } + i++; // next symbol, i=2,3 + GetNextNumber(i,j); // i=3,4 + if (i < nStringsCnt && !ScanEndString( sStrArray[i] )) + { + return false; + } + if (eSetType == SvNumFormatType::FRACTION) // -1,200. as fraction + { + if (!bNegCheck && // no sign '(' + eScannedType == SvNumFormatType::UNDEFINED && + (nDecPos == 0 || nDecPos == 3) // no decimal separator or at end + ) + { + eScannedType = SvNumFormatType::FRACTION; + nMatchedAllStrings &= ~nMatchedVirgin; + return true; + } + } + break; + case 3 : // Exactly 3 numbers in input + // nStringsCnt >= 5 + if (!GetNextNumber(i,j)) // i=1,0 + { // Analyze start string + if (!ScanStartString( sStrArray[i] )) + { + return false; // already an error + } + i++; // i=1 + if (nDecPos == 1) // decimal separator at start => error + { + return false; + } + } + GetNextNumber(i,j); // i=1,2 + if ( !ScanMidString( sStrArray[i], i ) ) + { + return false; + } + i++; // i=2,3 + if (eScannedType == SvNumFormatType::SCIENTIFIC) // E only at end + { + return false; + } + GetNextNumber(i,j); // i=3,4 + if ( !ScanMidString( sStrArray[i], i ) ) + { + return false; + } + i++; // i=4,5 + GetNextNumber(i,j); // i=5,6 + if (i < nStringsCnt && !ScanEndString( sStrArray[i] )) + { + return false; + } + if (eSetType == SvNumFormatType::FRACTION) // -1,200,100. as fraction + { + if (!bNegCheck && // no sign '(' + eScannedType == SvNumFormatType::UNDEFINED && + (nDecPos == 0 || nDecPos == 3) // no decimal separator or at end + ) + { + eScannedType = SvNumFormatType::FRACTION; + nMatchedAllStrings &= ~nMatchedVirgin; + return true; + } + } + if ( eScannedType == SvNumFormatType::FRACTION && nDecPos ) + { + return false; // #36857# not a real fraction + } + break; + default: // More than 3 numbers in input + // nStringsCnt >= 7 + if (!GetNextNumber(i,j)) // i=1,0 + { // Analyze startstring + if (!ScanStartString( sStrArray[i] )) + return false; // already an error + i++; // i=1 + if (nDecPos == 1) // decimal separator at start => error + return false; + } + GetNextNumber(i,j); // i=1,2 + if ( !ScanMidString( sStrArray[i], i ) ) + { + return false; + } + i++; // i=2,3 + { + sal_uInt16 nThOld = 10; // just not 0 or 1 + while (nThOld != nThousand && j < nNumericsCnt-1) // Execute at least one time + // but leave one number. + { // Loop over group separators + nThOld = nThousand; + if (eScannedType == SvNumFormatType::SCIENTIFIC) // E only at end + { + return false; + } + GetNextNumber(i,j); + if ( i < nStringsCnt && !ScanMidString( sStrArray[i], i ) ) + { + return false; + } + i++; + } + } + if (eScannedType == SvNumFormatType::DATE || // long date or + eScannedType == SvNumFormatType::TIME || // long time or + eScannedType == SvNumFormatType::UNDEFINED) // long number + { + for (sal_uInt16 k = j; k < nNumericsCnt-1; k++) + { + if (eScannedType == SvNumFormatType::SCIENTIFIC) // E only at endd + { + return false; + } + GetNextNumber(i,j); + if ( i < nStringsCnt && !ScanMidString( sStrArray[i], i ) ) + { + return false; + } + i++; + } + } + GetNextNumber(i,j); + if (i < nStringsCnt && !ScanEndString( sStrArray[i] )) + { + return false; + } + if (eSetType == SvNumFormatType::FRACTION) // -1,200,100. as fraction + { + if (!bNegCheck && // no sign '(' + eScannedType == SvNumFormatType::UNDEFINED && + (nDecPos == 0 || nDecPos == 3) // no decimal separator or at end + ) + { + eScannedType = SvNumFormatType::FRACTION; + nMatchedAllStrings &= ~nMatchedVirgin; + return true; + } + } + if ( eScannedType == SvNumFormatType::FRACTION && nDecPos ) + { + return false; // #36857# not a real fraction + } + break; + } + + if (eScannedType == SvNumFormatType::UNDEFINED) + { + nMatchedAllStrings &= ~nMatchedVirgin; + // did match including nMatchedUsedAsReturn + bool bDidMatch = (nMatchedAllStrings != 0); + if ( nMatchedAllStrings ) + { + bool bMatch = mpFormat && mpFormat->IsNumForStringElementCountEqual( + nStringScanNumFor, nStringsCnt, nNumericsCnt ); + if ( !bMatch ) + { + nMatchedAllStrings = 0; + } + } + if ( nMatchedAllStrings ) + { + // A type DEFINED means that no category could be assigned to the + // overall format because of mixed type subformats. Use the scan + // matched subformat's type if any. + SvNumFormatType eForType = eSetType; + if ((eForType == SvNumFormatType::UNDEFINED || eForType == SvNumFormatType::DEFINED) && mpFormat) + eForType = mpFormat->GetNumForInfoScannedType( nStringScanNumFor); + if (eForType != SvNumFormatType::UNDEFINED && eForType != SvNumFormatType::DEFINED) + eScannedType = eForType; + else + eScannedType = SvNumFormatType::NUMBER; + } + else if ( bDidMatch ) + { + // Accept a plain fractional number like 123.45 as there may be a + // decimal separator also present as literal like in a 0"."0 weirdo + // format. + if (nDecPos != 2 || nNumericsCnt != 2) + return false; + eScannedType = SvNumFormatType::NUMBER; + } + else + { + eScannedType = SvNumFormatType::NUMBER; + // everything else should have been recognized by now + } + } + else if ( eScannedType == SvNumFormatType::DATE ) + { + // the very relaxed date input checks may interfere with a preset format + nMatchedAllStrings &= ~nMatchedVirgin; + bool bWasReturn = ((nMatchedAllStrings & nMatchedUsedAsReturn) != 0); + if ( nMatchedAllStrings ) + { + bool bMatch = mpFormat && mpFormat->IsNumForStringElementCountEqual( + nStringScanNumFor, nStringsCnt, nNumericsCnt ); + if ( !bMatch ) + { + nMatchedAllStrings = 0; + } + } + if ( nMatchedAllStrings ) + { + // A type DEFINED means that no category could be assigned to the + // overall format because of mixed type subformats. Do not override + // the scanned type in this case. Otherwise in IsNumberFormat() the + // first numeric particle would be accepted as number. + SvNumFormatType eForType = eSetType; + if ((eForType == SvNumFormatType::UNDEFINED || eForType == SvNumFormatType::DEFINED) && mpFormat) + eForType = mpFormat->GetNumForInfoScannedType( nStringScanNumFor); + if (eForType != SvNumFormatType::UNDEFINED && eForType != SvNumFormatType::DEFINED) + eScannedType = eForType; + } + else if ( bWasReturn ) + { + return false; + } + } + else + { + nMatchedAllStrings = 0; // reset flag to no substrings matched + } + return true; +} + + +/** + * Return true or false depending on the nMatched... state and remember usage + */ +bool ImpSvNumberInputScan::MatchedReturn() +{ + if ( nMatchedAllStrings & ~nMatchedVirgin ) + { + nMatchedAllStrings |= nMatchedUsedAsReturn; + return true; + } + return false; +} + + +/** + * Initialize uppercase months and weekdays + */ +void ImpSvNumberInputScan::InitText() +{ + sal_Int32 j, nElems; + const CharClass* pChrCls = pFormatter->GetCharClass(); + const CalendarWrapper* pCal = pFormatter->GetCalendar(); + + pUpperMonthText.reset(); + pUpperAbbrevMonthText.reset(); + css::uno::Sequence< css::i18n::CalendarItem2 > xElems = pCal->getMonths(); + nElems = xElems.getLength(); + pUpperMonthText.reset( new OUString[nElems] ); + pUpperAbbrevMonthText.reset( new OUString[nElems] ); + for ( j = 0; j < nElems; j++ ) + { + pUpperMonthText[j] = pChrCls->uppercase( xElems[j].FullName ); + pUpperAbbrevMonthText[j] = pChrCls->uppercase( xElems[j].AbbrevName ); + } + + pUpperGenitiveMonthText.reset(); + pUpperGenitiveAbbrevMonthText.reset(); + xElems = pCal->getGenitiveMonths(); + bScanGenitiveMonths = (nElems != xElems.getLength()); + nElems = xElems.getLength(); + pUpperGenitiveMonthText.reset( new OUString[nElems] ); + pUpperGenitiveAbbrevMonthText.reset( new OUString[nElems] ); + for ( j = 0; j < nElems; j++ ) + { + pUpperGenitiveMonthText[j] = pChrCls->uppercase( xElems[j].FullName ); + pUpperGenitiveAbbrevMonthText[j] = pChrCls->uppercase( xElems[j].AbbrevName ); + if (!bScanGenitiveMonths && + (pUpperGenitiveMonthText[j] != pUpperMonthText[j] || + pUpperGenitiveAbbrevMonthText[j] != pUpperAbbrevMonthText[j])) + { + bScanGenitiveMonths = true; + } + } + + pUpperPartitiveMonthText.reset(); + pUpperPartitiveAbbrevMonthText.reset(); + xElems = pCal->getPartitiveMonths(); + bScanPartitiveMonths = (nElems != xElems.getLength()); + nElems = xElems.getLength(); + pUpperPartitiveMonthText.reset( new OUString[nElems] ); + pUpperPartitiveAbbrevMonthText.reset( new OUString[nElems] ); + for ( j = 0; j < nElems; j++ ) + { + pUpperPartitiveMonthText[j] = pChrCls->uppercase( xElems[j].FullName ); + pUpperPartitiveAbbrevMonthText[j] = pChrCls->uppercase( xElems[j].AbbrevName ); + if (!bScanPartitiveMonths && + (pUpperPartitiveMonthText[j] != pUpperGenitiveMonthText[j] || + pUpperPartitiveAbbrevMonthText[j] != pUpperGenitiveAbbrevMonthText[j])) + { + bScanPartitiveMonths = true; + } + } + + pUpperDayText.reset(); + pUpperAbbrevDayText.reset(); + xElems = pCal->getDays(); + nElems = xElems.getLength(); + pUpperDayText.reset( new OUString[nElems] ); + pUpperAbbrevDayText.reset( new OUString[nElems] ); + for ( j = 0; j < nElems; j++ ) + { + pUpperDayText[j] = pChrCls->uppercase( xElems[j].FullName ); + pUpperAbbrevDayText[j] = pChrCls->uppercase( xElems[j].AbbrevName ); + } + + bTextInitialized = true; +} + + +/** + * MUST be called if International/Locale is changed + */ +void ImpSvNumberInputScan::ChangeIntl() +{ + sal_Unicode cDecSep = pFormatter->GetNumDecimalSep()[0]; + bDecSepInDateSeps = ( cDecSep == '-' || + cDecSep == pFormatter->GetDateSep()[0] ); + if (!bDecSepInDateSeps) + { + sal_Unicode cDecSepAlt = pFormatter->GetNumDecimalSepAlt().toChar(); + bDecSepInDateSeps = cDecSepAlt && (cDecSepAlt == '-' || cDecSepAlt == pFormatter->GetDateSep()[0]); + } + bTextInitialized = false; + aUpperCurrSymbol.clear(); + InvalidateDateAcceptancePatterns(); +} + + +void ImpSvNumberInputScan::InvalidateDateAcceptancePatterns() +{ + if (sDateAcceptancePatterns.hasElements()) + { + sDateAcceptancePatterns = css::uno::Sequence< OUString >(); + } +} + + +void ImpSvNumberInputScan::ChangeNullDate( const sal_uInt16 Day, + const sal_uInt16 Month, + const sal_Int16 Year ) +{ + if ( pNullDate ) + { + *pNullDate = Date(Day, Month, Year); + } + else + { + pNullDate.reset(new Date(Day, Month, Year)); + } +} + + +/** + * Does rString represent a number (also date, time et al) + */ +bool ImpSvNumberInputScan::IsNumberFormat( const OUString& rString, // string to be analyzed + SvNumFormatType& F_Type, // IN: old type, OUT: new type + double& fOutNumber, // OUT: number if convertible + const SvNumberformat* pFormat, // maybe a number format to match against + SvNumInputOptions eInputOptions ) +{ + bool res; // return value + sal_uInt16 k; + eSetType = F_Type; // old type set + + if ( !rString.getLength() ) + { + res = false; + } + else if (rString.getLength() > 308) // arbitrary + { + res = false; + } + else + { + // NoMoreUpperNeeded, all comparisons on UpperCase + OUString aString = pFormatter->GetCharClass()->uppercase( rString ); + // convert native number to ASCII if necessary + TransformInput(pFormatter, aString); + res = IsNumberFormatMain( aString, pFormat ); + } + + if (res) + { + // Accept signed date only for ISO date with at least four digits in + // year to not have an input of -M-D-Y arbitrarily recognized. The + // final order is only determined in GetDateRef(). + // Also accept for Y/M/D date pattern match, i.e. if the first number + // is year. + // Accept only if the year immediately follows the sign character with + // no space in between. + if (nSign && (eScannedType == SvNumFormatType::DATE || + eScannedType == SvNumFormatType::DATETIME) && mbEraCE == kDefaultEra && + (IsDatePatternNumberOfType(0,'Y') || (MayBeIso8601() && sStrArray[nNums[0]].getLength() >= 4))) + { + const sal_Unicode c = sStrArray[0][sStrArray[0].getLength()-1]; + if (c == '-' || c == '+') + { + // A '+' sign doesn't change the era. + if (nSign < 0) + mbEraCE = false; // BCE + nSign = 0; + } + } + if ( bNegCheck || // ')' not found for '(' + (nSign && (eScannedType == SvNumFormatType::DATE || + eScannedType == SvNumFormatType::DATETIME))) // signed date/datetime + { + res = false; + } + else + { // check count of partial number strings + switch (eScannedType) + { + case SvNumFormatType::PERCENT: + case SvNumFormatType::CURRENCY: + case SvNumFormatType::NUMBER: + if (nDecPos == 1) // .05 + { + // Matched MidStrings function like group separators, but + // there can't be an integer part numeric input, so + // effectively 0 thousands groups. + if ( nMatchedAllStrings ) + { + nThousand = 0; + } + else if ( nNumericsCnt != 1 ) + { + res = false; + } + } + else if (nDecPos == 2) // 1.05 + { + // Matched MidStrings function like group separators, but + // let a decimal separator override a literal separator + // string; like 0"." with input 123.45 + if ( nMatchedAllStrings ) + { + if (nNumericsCnt == 2) + nThousand = 0; + else + { + // Assume that if there was a decimal separator + // matching also a literal string then it was the + // last. We could find the last possible match to + // support literals in fractions, but really.. + nThousand = nNumericsCnt - 1; + } + } + else if ( nNumericsCnt != nThousand+2 ) + { + res = false; + } + } + else // 1,100 or 1,100. + { + // matched MidStrings function like group separators + if ( nMatchedAllStrings ) + { + nThousand = nNumericsCnt - 1; + } + else if ( nNumericsCnt != nThousand+1 ) + { + res = false; + } + } + break; + + case SvNumFormatType::SCIENTIFIC: // 1.0e-2 + if (nDecPos == 1) // .05 + { + if (nNumericsCnt != 2) + { + res = false; + } + } + else if (nDecPos == 2) // 1.05 + { + if (nNumericsCnt != nThousand+3) + { + res = false; + } + } + else // 1,100 or 1,100. + { + if (nNumericsCnt != nThousand+2) + { + res = false; + } + } + break; + + case SvNumFormatType::DATE: + if (nMonth < 0 && nDayOfWeek < 0 && nNumericsCnt == 3) + { + // If both, short month name and day of week name were + // detected, and also numbers for full date, assume that we + // have a day of week instead of month name. + nMonth = 0; + nMonthPos = 0; + } + if (nMonth) + { // month name and numbers + if (nNumericsCnt > 2) + { + res = false; + } + } + else + { + if (nNumericsCnt > 3) + { + res = false; + } + else + { + // Even if a date pattern was matched, for abbreviated + // pattern like "D.M." an input of "D.M. #" was + // accepted because # could had been a time. Here we do + // not have a combined date/time input though and # + // would be taken as Year in this example, which it is + // not. The count of numbers in pattern must match the + // count of numbers in input. + res = (GetDatePatternNumbers() == nNumericsCnt) + || IsAcceptableIso8601() || nMatchedAllStrings; + } + } + break; + + case SvNumFormatType::TIME: + if (nDecPos) + { // hundredth seconds included + if (nNumericsCnt > 4) + { + res = false; + } + } + else + { + if (nNumericsCnt > 3) + { + res = false; + } + } + break; + + case SvNumFormatType::DATETIME: + if (nMonth < 0 && nDayOfWeek < 0 && nNumericsCnt >= 5) + { + // If both, abbreviated month name and day of week name + // were detected, and also at least numbers for full date + // plus time including minutes, assume that we have a day + // of week instead of month name. + nMonth = 0; + nMonthPos = 0; + } + if (nMonth) + { // month name and numbers + if (nDecPos) + { // hundredth seconds included + if (nNumericsCnt > 6) + { + res = false; + } + } + else + { + if (nNumericsCnt > 5) + { + res = false; + } + } + } + else + { + if (nDecPos) + { // hundredth seconds included + if (nNumericsCnt > 7) + { + res = false; + } + } + else + { + if (nNumericsCnt > 6) + { + res = false; + } + } + if (res) + { + res = IsAcceptedDatePattern( nNums[0]) || MayBeIso8601() || nMatchedAllStrings; + } + } + break; + + default: + break; + } // switch + } // else + } // if (res) + + OUStringBuffer sResString; + + if (res) + { // we finally have a number + switch (eScannedType) + { + case SvNumFormatType::LOGICAL: + if (nLogical == 1) + { + fOutNumber = 1.0; // True + } + else if (nLogical == -1) + { + fOutNumber = 0.0; // False + } + else + { + res = false; // Oops + } + break; + + case SvNumFormatType::PERCENT: + case SvNumFormatType::CURRENCY: + case SvNumFormatType::NUMBER: + case SvNumFormatType::SCIENTIFIC: + case SvNumFormatType::DEFINED: // if no category detected handle as number + if ( nDecPos == 1 ) // . at start + { + sResString.append("0."); + } + + for ( k = 0; k <= nThousand; k++) + { + sResString.append(sStrArray[nNums[k]]); // integer part + } + if ( nDecPos == 2 && k < nNumericsCnt ) // . somewhere + { + sResString.append('.'); + sal_uInt16 nStop = (eScannedType == SvNumFormatType::SCIENTIFIC ? + nNumericsCnt-1 : nNumericsCnt); + for ( ; k < nStop; k++) + { + sResString.append(sStrArray[nNums[k]]); // fractional part + } + } + + if (eScannedType != SvNumFormatType::SCIENTIFIC) + { + fOutNumber = StringToDouble(sResString); + } + else + { // append exponent + sResString.append('E'); + if ( nESign == -1 ) + { + sResString.append('-'); + } + sResString.append(sStrArray[nNums[nNumericsCnt-1]]); + rtl_math_ConversionStatus eStatus; + fOutNumber = ::rtl::math::stringToDouble( sResString, '.', ',', &eStatus ); + if ( eStatus == rtl_math_ConversionStatus_OutOfRange ) + { + F_Type = SvNumFormatType::TEXT; // overflow/underflow -> Text + if (nESign == -1) + { + fOutNumber = 0.0; + } + else + { + fOutNumber = DBL_MAX; + } + return true; + } + } + + if ( nStringScanSign ) + { + if ( nSign ) + { + nSign *= nStringScanSign; + } + else + { + nSign = nStringScanSign; + } + } + if ( nSign < 0 ) + { + fOutNumber = -fOutNumber; + } + + if (eScannedType == SvNumFormatType::PERCENT) + { + fOutNumber/= 100.0; + } + break; + + case SvNumFormatType::FRACTION: + if (nNumericsCnt == 1) + { + fOutNumber = StringToDouble(sStrArray[nNums[0]]); + } + else if (nNumericsCnt == 2) + { + if (nThousand == 1) + { + sResString = sStrArray[nNums[0]]; + sResString.append(sStrArray[nNums[1]]); // integer part + fOutNumber = StringToDouble(sResString); + } + else + { + double fNumerator = StringToDouble(sStrArray[nNums[0]]); + double fDenominator = StringToDouble(sStrArray[nNums[1]]); + if (fDenominator != 0.0) + { + fOutNumber = fNumerator/fDenominator; + } + else + { + res = false; + } + } + } + else // nNumericsCnt > 2 + { + k = 1; + sResString = sStrArray[nNums[0]]; + if (nThousand > 0) + { + for (; k <= nThousand; k++) + { + sResString.append(sStrArray[nNums[k]]); + } + } + fOutNumber = StringToDouble(sResString); + + if (k == nNumericsCnt-2) + { + double fNumerator = StringToDouble(sStrArray[nNums[k]]); + double fDenominator = StringToDouble(sStrArray[nNums[k + 1]]); + if (fDenominator != 0.0) + { + fOutNumber += fNumerator/fDenominator; + } + else + { + res = false; + } + } + } + + if ( nStringScanSign ) + { + if ( nSign ) + { + nSign *= nStringScanSign; + } + else + { + nSign = nStringScanSign; + } + } + if ( nSign < 0 ) + { + fOutNumber = -fOutNumber; + } + break; + + case SvNumFormatType::TIME: + res = GetTimeRef(fOutNumber, 0, nNumericsCnt, eInputOptions); + if ( nSign < 0 ) + { + fOutNumber = -fOutNumber; + } + break; + + case SvNumFormatType::DATE: + res = GetDateRef( fOutNumber, k ); + break; + + case SvNumFormatType::DATETIME: + res = GetDateRef( fOutNumber, k ); + if ( res ) + { + double fTime; + res = GetTimeRef( fTime, k, nNumericsCnt - k, eInputOptions); + fOutNumber += fTime; + } + break; + + default: + SAL_WARN( "svl.numbers", "Some number recognized but what's it?" ); + fOutNumber = 0.0; + break; + } + } + + if (res) // overflow/underflow -> Text + { + if (fOutNumber < -DBL_MAX) // -1.7E308 + { + F_Type = SvNumFormatType::TEXT; + fOutNumber = -DBL_MAX; + return true; + } + else if (fOutNumber > DBL_MAX) // 1.7E308 + { + F_Type = SvNumFormatType::TEXT; + fOutNumber = DBL_MAX; + return true; + } + } + + if (!res) + { + eScannedType = SvNumFormatType::TEXT; + fOutNumber = 0.0; + } + + F_Type = eScannedType; + return res; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/numbers/zforfind.hxx b/svl/source/numbers/zforfind.hxx new file mode 100644 index 000000000..7e6ddeb59 --- /dev/null +++ b/svl/source/numbers/zforfind.hxx @@ -0,0 +1,441 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVL_SOURCE_NUMBERS_ZFORFIND_HXX +#define INCLUDED_SVL_SOURCE_NUMBERS_ZFORFIND_HXX + +#include +#include +#include +#include + +class Date; +class SvNumberformat; +class SvNumberFormatter; +enum class SvNumFormatType : sal_Int16; + +#define SV_MAX_COUNT_INPUT_STRINGS 20 // max count of substrings in input scanner + +class ImpSvNumberInputScan +{ +public: + explicit ImpSvNumberInputScan( SvNumberFormatter* pFormatter ); + ~ImpSvNumberInputScan(); + +/*!*/ void ChangeIntl(); // MUST be called if language changes + + /// set reference date for offset calculation + void ChangeNullDate( const sal_uInt16 nDay, + const sal_uInt16 nMonth, + const sal_Int16 nYear ); + + /// convert input string to number + bool IsNumberFormat( const OUString& rString, /// input string + SvNumFormatType& F_Type, /// format type (in + out) + double& fOutNumber, /// value determined (out) + const SvNumberformat* pFormat, /// number format to which compare against + SvNumInputOptions eInputOptions); + + /// after IsNumberFormat: get decimal position + short GetDecPos() const { return nDecPos; } + /// after IsNumberFormat: get count of numeric substrings in input string + sal_uInt16 GetNumericsCount() const { return nNumericsCnt; } + + /// set threshold of two-digit year input + void SetYear2000( sal_uInt16 nVal ) { nYear2000 = nVal; } + /// get threshold of two-digit year input + sal_uInt16 GetYear2000() const { return nYear2000; } + + /** Whether input can be forced to ISO 8601 format. + + Depends on locale's date separator and a specific date format order. + */ + bool CanForceToIso8601( DateOrder eDateOrder ); + + void InvalidateDateAcceptancePatterns(); + + /** Whether 'T' separator was detected in an ISO 8601 date+time format. + */ + bool HasIso8601Tsep() const { return bIso8601Tsep; } + +private: + SvNumberFormatter* pFormatter; + const SvNumberformat* mpFormat; //* The format to compare against, if any + std::unique_ptr pUpperMonthText; //* Array of month names, uppercase + std::unique_ptr pUpperAbbrevMonthText; //* Array of month names, abbreviated, uppercase + std::unique_ptr pUpperGenitiveMonthText; //* Array of genitive month names, uppercase + std::unique_ptr pUpperGenitiveAbbrevMonthText; //* Array of genitive month names, abbreviated, uppercase + std::unique_ptr pUpperPartitiveMonthText; //* Array of partitive month names, uppercase + std::unique_ptr pUpperPartitiveAbbrevMonthText;//* Array of partitive month names, abbreviated, uppercase + std::unique_ptr pUpperDayText; //* Array of day of week names, uppercase + std::unique_ptr pUpperAbbrevDayText; //* Array of day of week names, abbreviated, uppercase + OUString aUpperCurrSymbol; //* Currency symbol, uppercase + bool bTextInitialized; //* Whether days and months are initialized + bool bScanGenitiveMonths; //* Whether to scan an input for genitive months + bool bScanPartitiveMonths; //* Whether to scan an input for partitive months + std::unique_ptr pNullDate; //* 30Dec1899 + // Variables for provisional results: + OUString sStrArray[SV_MAX_COUNT_INPUT_STRINGS];//* Array of scanned substrings + bool IsNum[SV_MAX_COUNT_INPUT_STRINGS]; //* Whether a substring is numeric + sal_uInt16 nNums[SV_MAX_COUNT_INPUT_STRINGS]; //* Sequence of offsets to numeric strings + sal_uInt16 nStringsCnt; //* Total count of scanned substrings + sal_uInt16 nNumericsCnt; //* Count of numeric substrings + bool bDecSepInDateSeps; //* True <=> DecSep in {.,-,/,DateSep} + sal_uInt8 nMatchedAllStrings; //* Scan...String() matched all substrings, + + // bit mask of nMatched... constants + static const sal_uInt8 nMatchedEndString; // 0x01 + static const sal_uInt8 nMatchedMidString; // 0x02 + static const sal_uInt8 nMatchedStartString; // 0x04 + static const sal_uInt8 nMatchedVirgin; // 0x08 + static const sal_uInt8 nMatchedUsedAsReturn; // 0x10 + + int nSign; // Sign of number + int nMonth; // Month (1..x) if date + // negative => short format + short nMonthPos; // 1 = front, 2 = middle + // 3 = end + int nDayOfWeek; // Temporary (!) day of week (1..7,-1..-7) if date + sal_uInt16 nTimePos; // Index of first time separator (+1) + short nDecPos; // Index of substring containing "," (+1) + bool bNegCheck; // '( )' for negative + short nESign; // Sign of exponent + short nAmPm; // +1 AM, -1 PM, 0 if none + short nLogical; // -1 => False, 1 => True + bool mbEraCE; // Era if date, 0 => BCE, 1 => CE (currently only Gregorian) + sal_uInt16 nThousand; // Count of group (AKA thousand) separators + sal_uInt16 nPosThousandString; // Position of concatenated 000,000,000 string + SvNumFormatType eScannedType; // Scanned type + SvNumFormatType eSetType; // Preset Type + + sal_uInt16 nStringScanNumFor; // Fixed strings recognized in + // pFormat->NumFor[nNumForStringScan] + short nStringScanSign; // Sign resulting of FixString + sal_uInt16 nYear2000; // Two-digit threshold + // Year as 20xx + // default 18 + // number <= nYear2000 => 20xx + // number > nYear2000 => 19xx + + /** State of ISO 8601 detection. + + 0:= don't know yet + 1:= no + 2:= yes, <=2 digits in year + 3:= yes, 3 digits in year + 4:= yes, >=4 digits in year + + @see MayBeIso8601() + */ + sal_uInt8 nMayBeIso8601; + + /** Whether the 'T' time separator was detected in an ISO 8601 string. */ + bool bIso8601Tsep; + + /** State of dd-month-yy or yy-month-dd detection, with month name. + + 0:= don't know yet + 1:= no + 2:= yes, dd-month-yy + 3:= yes, yy-month-dd + + @see MayBeMonthDate() + */ + sal_uInt8 nMayBeMonthDate; + + /** Input matched this locale dependent date acceptance pattern. + -2 if not checked yet, -1 if no match, >=0 matched pattern. + + @see IsAcceptedDatePattern() + */ + sal_Int32 nAcceptedDatePattern; + css::uno::Sequence< OUString > sDateAcceptancePatterns; + + /** If input matched a date acceptance pattern that starts at input + particle sStrArray[nDatePatternStart]. + + @see IsAcceptedDatePattern() + */ + sal_uInt16 nDatePatternStart; + + /** Count of numbers that matched the accepted pattern, if any, else 0. + + @see GetDatePatternNumbers() + */ + sal_uInt16 nDatePatternNumbers; + + // Copy assignment is forbidden and not implemented. + ImpSvNumberInputScan (const ImpSvNumberInputScan &) = delete; + ImpSvNumberInputScan & operator= (const ImpSvNumberInputScan &) = delete; + + void Reset(); // Reset all variables before start of analysis + + void InitText(); // Init of months and days of week + + // Convert string to double. + // Only simple unsigned floating point values without any error detection, + // decimal separator has to be '.' + // If bForceFraction==true the string is taken to be the fractional part + // of 0.1234 without the leading 0. (thus being just "1234"). + static double StringToDouble( std::u16string_view aStr, + bool bForceFraction = false ); + + // Next number/string symbol + static bool NextNumberStringSymbol( const sal_Unicode*& pStr, + OUString& rSymbol ); + + // Concatenate ,000,23 blocks + // in input to 000123 + bool SkipThousands( const sal_Unicode*& pStr, OUString& rSymbol ) const; + + // Divide numbers/strings into + // arrays and variables above. + // Leading blanks and blanks + // after numbers are thrown away + void NumberStringDivision( const OUString& rString ); + + + /** Whether rString contains word (!) rWhat at nPos. + rWhat will not be matched if it is a substring of a word. + */ + bool StringContainsWord( const OUString& rWhat, + const OUString& rString, + sal_Int32 nPos ) const; + + // optimized substring versions + + // Whether rString contains rWhat at nPos + static bool StringContains( const OUString& rWhat, + const OUString& rString, + sal_Int32 nPos ) + { + if (rWhat.isEmpty() || rString.getLength() <= nPos) + { + return false; + } + // mostly used with one character + if ( rWhat[ 0 ] != rString[ nPos ] ) + { + return false; + } + return StringContainsImpl( rWhat, rString, nPos ); + } + + // Whether pString contains rWhat at nPos + static bool StringPtrContains( const OUString& rWhat, + const sal_Unicode* pString, + sal_Int32 nPos ) // nPos MUST be a valid offset from pString + { + // mostly used with one character + if ( rWhat[ 0 ] != pString[ nPos ] ) + { + return false; + } + return StringPtrContainsImpl( rWhat, pString, nPos ); + } + + //! DO NOT use directly + static bool StringContainsImpl( const OUString& rWhat, + const OUString& rString, + sal_Int32 nPos ); + //! DO NOT use directly + static bool StringPtrContainsImpl( const OUString& rWhat, + const sal_Unicode* pString, + sal_Int32 nPos ); + + // Skip a special character + static inline bool SkipChar( sal_Unicode c, + const OUString& rString, + sal_Int32& nPos ); + + // Skip blank + static inline bool SkipBlanks( const OUString& rString, + sal_Int32& nPos ); + + // Jump over rWhat in rString at nPos + static inline bool SkipString( const OUString& rWhat, + const OUString& rString, + sal_Int32& nPos ); + + // Recognizes exactly ,111 as group separator + inline bool GetThousandSep( const OUString& rString, + sal_Int32& nPos, + sal_uInt16 nStringPos ) const; + // Get boolean value + short GetLogical( std::u16string_view rString ) const; + + // Get month and advance string position + short GetMonth( const OUString& rString, + sal_Int32& nPos ); + + // Get day of week and advance string position + int GetDayOfWeek( const OUString& rString, + sal_Int32& nPos ); + + // Get currency symbol and advance string position + bool GetCurrency( const OUString& rString, + sal_Int32& nPos ); + + // Get symbol AM or PM and advance string position + bool GetTimeAmPm( const OUString& rString, + sal_Int32& nPos ); + + // Get decimal separator and advance string position + inline bool GetDecSep( const OUString& rString, + sal_Int32& nPos ) const; + + // Get hundredth seconds separator and advance string position + inline bool GetTime100SecSep( const OUString& rString, + sal_Int32& nPos ) const; + + // Get sign and advance string position + // Including special case '(' + int GetSign( const OUString& rString, + sal_Int32& nPos ); + + // Get sign of exponent and advance string position + static short GetESign( const OUString& rString, + sal_Int32& nPos ); + + // Get next number as array offset + inline bool GetNextNumber( sal_uInt16& i, + sal_uInt16& j ) const; + + /** Converts time -> double (only decimals) + + @return TRUE if time, FALSE if not (e.g. hours >12 with AM/PM) + */ + bool GetTimeRef( double& fOutNumber, // result as double + sal_uInt16 nIndex, // Index of hour in input + sal_uInt16 nCnt, // Count of time substrings in input + SvNumInputOptions eInputOptions ) const; + sal_uInt16 ImplGetDay ( sal_uInt16 nIndex ) const; // Day input, 0 if no match + sal_uInt16 ImplGetMonth( sal_uInt16 nIndex ) const; // Month input, zero based return, NumberOfMonths if no match + sal_uInt16 ImplGetYear ( sal_uInt16 nIndex ); // Year input, 0 if no match + + // Conversion of date to number + bool GetDateRef( double& fDays, // OUT: days diff to null date + sal_uInt16& nCounter ); // Count of date substrings + + // Analyze start of string + bool ScanStartString( const OUString& rString ); + + // Analyze middle substring + bool ScanMidString( const OUString& rString, + sal_uInt16 nStringPos ); + + + // Analyze end of string + bool ScanEndString( const OUString& rString ); + + // Compare rString to substring of array indexed by nString + // nString == 0xFFFF => last substring + bool ScanStringNumFor( const OUString& rString, + sal_Int32 nPos, + sal_uInt16 nString, + bool bDontDetectNegation = false ); + + // if nMatchedAllStrings set nMatchedUsedAsReturn and return true, + // else do nothing and return false + bool MatchedReturn(); + + //! Be sure that the string to be analyzed is already converted to upper + //! case and if it contained native number digits that they are already + //! converted to ASCII. + + // Main analyzing function + bool IsNumberFormatMain( const OUString& rString, + const SvNumberformat* pFormat); // number format to match against + + /** Whether input matches locale dependent date acceptance pattern. + + @param nStartPatternAt + The pattern matching starts at input particle + sStrArray[nStartPatternAt]. + + NOTE: once called the result is remembered, subsequent calls with + different parameters do not check for a match and do not lead to a + different result. + */ + bool IsAcceptedDatePattern( sal_uInt16 nStartPatternAt ); + + /** Sets (not advances!) rPos to sStrArray[nParticle].getLength() if string + matches separator in pattern at nParticle. + + Also detects a signed year case like M/D/-Y + + @returns TRUE if separator matched. + */ + bool SkipDatePatternSeparator( sal_uInt16 nParticle, sal_Int32 & rPos, bool & rSignedYear ); + + /** Returns count of numbers in accepted date pattern. + */ + sal_uInt16 GetDatePatternNumbers(); + + /** Whether numeric string nNumber is of type cType in accepted date + pattern, 'Y', 'M' or 'D'. + */ + bool IsDatePatternNumberOfType( sal_uInt16 nNumber, sal_Unicode cType ); + + /** Obtain order of accepted date pattern coded as, for example, + ('D'<<16)|('M'<<8)|'Y' + */ + sal_uInt32 GetDatePatternOrder(); + + /** Obtain date format order, from accepted date pattern if available or + otherwise the locale's default order. + + @param bFromFormatIfNoPattern + If and no pattern was matched, obtain date order from + format if available, instead from format's or current locale. + */ + DateOrder GetDateOrder( bool bFromFormatIfNoPattern = false ); + + /** Whether input may be an ISO 8601 date format, yyyy-mm-dd... + + Checks if input has at least 3 numbers for yyyy-mm-dd and the separator + is '-', and 1<=mm<=12 and 1<=dd<=31. + + @see nMayBeIso8601 + */ + bool MayBeIso8601(); + + /** Whether input may be a dd-month-yy format, with month name, not + number. + + @see nMayBeMonthDate + */ + bool MayBeMonthDate(); + + /** Whether input is acceptable as ISO 8601 date format in the current + NfEvalDateFormat setting. + */ + bool IsAcceptableIso8601(); + + /** If month name in the middle was parsed, get the corresponding + LongDateOrder in GetDateRef(). + */ + LongDateOrder GetMiddleMonthLongDateOrder( bool bFormatTurn, + const LocaleDataWrapper* pLoc, + DateOrder eDateOrder ); +}; + +#endif // INCLUDED_SVL_SOURCE_NUMBERS_ZFORFIND_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/numbers/zforlist.cxx b/svl/source/numbers/zforlist.cxx new file mode 100644 index 000000000..e7de0d615 --- /dev/null +++ b/svl/source/numbers/zforlist.cxx @@ -0,0 +1,4972 @@ +/* -*- 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 + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "zforscan.hxx" +#include "zforfind.hxx" +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::lang; +using namespace ::std; + +// Constants for type offsets per Country/Language (CL) +#define ZF_STANDARD 0 +#define ZF_STANDARD_PERCENT 10 +#define ZF_STANDARD_CURRENCY 20 +#define ZF_STANDARD_DATE 30 +#define ZF_STANDARD_TIME 60 +#define ZF_STANDARD_DURATION (ZF_STANDARD_TIME + 4) +#define ZF_STANDARD_DATETIME 70 +#define ZF_STANDARD_SCIENTIFIC 80 +#define ZF_STANDARD_FRACTION 85 + +// Additional builtin formats, historically not fitting into the first 10 of a +// category. Make sure it doesn't spill over to the next category. +#define ZF_STANDARD_DATE_SYS_DMMMYYYY (ZF_STANDARD_DATE + 10) +#define ZF_STANDARD_DATE_SYS_DMMMMYYYY (ZF_STANDARD_DATE + 11) +#define ZF_STANDARD_DATE_SYS_NNDMMMYY (ZF_STANDARD_DATE + 12) +#define ZF_STANDARD_DATE_SYS_NNDMMMMYYYY (ZF_STANDARD_DATE + 13) +#define ZF_STANDARD_DATE_SYS_NNNNDMMMMYYYY (ZF_STANDARD_DATE + 14) +#define ZF_STANDARD_DATE_DIN_DMMMYYYY (ZF_STANDARD_DATE + 15) +#define ZF_STANDARD_DATE_DIN_DMMMMYYYY (ZF_STANDARD_DATE + 16) +#define ZF_STANDARD_DATE_DIN_MMDD (ZF_STANDARD_DATE + 17) +#define ZF_STANDARD_DATE_DIN_YYMMDD (ZF_STANDARD_DATE + 18) +#define ZF_STANDARD_DATE_DIN_YYYYMMDD (ZF_STANDARD_DATE + 19) +#define ZF_STANDARD_DATE_WW (ZF_STANDARD_DATE + 20) + +#define ZF_STANDARD_LOGICAL SV_MAX_COUNT_STANDARD_FORMATS-1 // 99 +#define ZF_STANDARD_TEXT SV_MAX_COUNT_STANDARD_FORMATS // 100 + +static_assert( ZF_STANDARD_TEXT == NF_STANDARD_FORMAT_TEXT, "definition mismatch" ); + +static_assert( NF_INDEX_TABLE_RESERVED_START == i18npool::nStopPredefinedFormatIndex, + "NfIndexTableOffset does not match i18npool's locale data predefined format code index bounds."); + +static_assert( NF_INDEX_TABLE_ENTRIES <= i18npool::nFirstFreeFormatIndex, + "NfIndexTableOffset crosses i18npool's locale data reserved format code index bounds.\n" + "You will need to adapt all locale data files defining index values " + "(formatIndex=\"...\") in that range and increment those and when done " + "adjust nFirstFreeFormatIndex in include/i18npool/reservedconstants.hxx"); + +/* Locale that is set if an unknown locale (from another system) is loaded of + * legacy documents. Can not be SYSTEM because else, for example, a German "DM" + * (old currency) is recognized as a date (#53155#). */ +#define UNKNOWN_SUBSTITUTE LANGUAGE_ENGLISH_US + +// Same order as in include/svl/zforlist.hxx enum NfIndexTableOffset +sal_uInt32 const indexTable[NF_INDEX_TABLE_ENTRIES] = { + ZF_STANDARD, // NF_NUMBER_STANDARD + ZF_STANDARD + 1, // NF_NUMBER_INT + ZF_STANDARD + 2, // NF_NUMBER_DEC2 + ZF_STANDARD + 3, // NF_NUMBER_1000INT + ZF_STANDARD + 4, // NF_NUMBER_1000DEC2 + ZF_STANDARD + 5, // NF_NUMBER_SYSTEM + ZF_STANDARD_SCIENTIFIC, // NF_SCIENTIFIC_000E000 + ZF_STANDARD_SCIENTIFIC + 1, // NF_SCIENTIFIC_000E00 + ZF_STANDARD_PERCENT, // NF_PERCENT_INT + ZF_STANDARD_PERCENT + 1, // NF_PERCENT_DEC2 + ZF_STANDARD_FRACTION, // NF_FRACTION_1D + ZF_STANDARD_FRACTION + 1, // NF_FRACTION_2D + ZF_STANDARD_CURRENCY, // NF_CURRENCY_1000INT + ZF_STANDARD_CURRENCY + 1, // NF_CURRENCY_1000DEC2 + ZF_STANDARD_CURRENCY + 2, // NF_CURRENCY_1000INT_RED + ZF_STANDARD_CURRENCY + 3, // NF_CURRENCY_1000DEC2_RED + ZF_STANDARD_CURRENCY + 4, // NF_CURRENCY_1000DEC2_CCC + ZF_STANDARD_CURRENCY + 5, // NF_CURRENCY_1000DEC2_DASHED + ZF_STANDARD_DATE, // NF_DATE_SYSTEM_SHORT + ZF_STANDARD_DATE + 8, // NF_DATE_SYSTEM_LONG + ZF_STANDARD_DATE + 7, // NF_DATE_SYS_DDMMYY + ZF_STANDARD_DATE + 6, // NF_DATE_SYS_DDMMYYYY + ZF_STANDARD_DATE + 9, // NF_DATE_SYS_DMMMYY + ZF_STANDARD_DATE_SYS_DMMMYYYY, // NF_DATE_SYS_DMMMYYYY + ZF_STANDARD_DATE_DIN_DMMMYYYY, // NF_DATE_DIN_DMMMYYYY + ZF_STANDARD_DATE_SYS_DMMMMYYYY, // NF_DATE_SYS_DMMMMYYYY + ZF_STANDARD_DATE_DIN_DMMMMYYYY, // NF_DATE_DIN_DMMMMYYYY + ZF_STANDARD_DATE_SYS_NNDMMMYY, // NF_DATE_SYS_NNDMMMYY + ZF_STANDARD_DATE + 1, // NF_DATE_DEF_NNDDMMMYY + ZF_STANDARD_DATE_SYS_NNDMMMMYYYY, // NF_DATE_SYS_NNDMMMMYYYY + ZF_STANDARD_DATE_SYS_NNNNDMMMMYYYY, // NF_DATE_SYS_NNNNDMMMMYYYY + ZF_STANDARD_DATE_DIN_MMDD, // NF_DATE_DIN_MMDD + ZF_STANDARD_DATE_DIN_YYMMDD, // NF_DATE_DIN_YYMMDD + ZF_STANDARD_DATE_DIN_YYYYMMDD, // NF_DATE_DIN_YYYYMMDD + ZF_STANDARD_DATE + 2, // NF_DATE_SYS_MMYY + ZF_STANDARD_DATE + 3, // NF_DATE_SYS_DDMMM + ZF_STANDARD_DATE + 4, // NF_DATE_MMMM + ZF_STANDARD_DATE + 5, // NF_DATE_QQJJ + ZF_STANDARD_DATE_WW, // NF_DATE_WW + ZF_STANDARD_TIME, // NF_TIME_HHMM + ZF_STANDARD_TIME + 1, // NF_TIME_HHMMSS + ZF_STANDARD_TIME + 2, // NF_TIME_HHMMAMPM + ZF_STANDARD_TIME + 3, // NF_TIME_HHMMSSAMPM + ZF_STANDARD_TIME + 4, // NF_TIME_HH_MMSS + ZF_STANDARD_TIME + 5, // NF_TIME_MMSS00 + ZF_STANDARD_TIME + 6, // NF_TIME_HH_MMSS00 + ZF_STANDARD_DATETIME, // NF_DATETIME_SYSTEM_SHORT_HHMM + ZF_STANDARD_DATETIME + 1, // NF_DATETIME_SYS_DDMMYYYY_HHMMSS + ZF_STANDARD_LOGICAL, // NF_BOOLEAN + ZF_STANDARD_TEXT, // NF_TEXT + ZF_STANDARD_DATETIME + 2, // NF_DATETIME_SYS_DDMMYYYY_HHMM + ZF_STANDARD_FRACTION + 2, // NF_FRACTION_3D + ZF_STANDARD_FRACTION + 3, // NF_FRACTION_2 + ZF_STANDARD_FRACTION + 4, // NF_FRACTION_4 + ZF_STANDARD_FRACTION + 5, // NF_FRACTION_8 + ZF_STANDARD_FRACTION + 6, // NF_FRACTION_16 + ZF_STANDARD_FRACTION + 7, // NF_FRACTION_10 + ZF_STANDARD_FRACTION + 8, // NF_FRACTION_100 + ZF_STANDARD_DATETIME + 3, // NF_DATETIME_ISO_YYYYMMDD_HHMMSS + ZF_STANDARD_DATETIME + 4, // NF_DATETIME_ISO_YYYYMMDD_HHMMSS000 + ZF_STANDARD_DATETIME + 5, // NF_DATETIME_ISO_YYYYMMDDTHHMMSS + ZF_STANDARD_DATETIME + 6 // NF_DATETIME_ISO_YYYYMMDDTHHMMSS000 +}; + +/** + instead of every number formatter being a listener we have a registry which + also handles one instance of the SysLocale options + */ + +class SvNumberFormatterRegistry_Impl : public utl::ConfigurationListener +{ + std::vector< SvNumberFormatter* > + aFormatters; + SvtSysLocaleOptions aSysLocaleOptions; + LanguageType eSysLanguage; + +public: + SvNumberFormatterRegistry_Impl(); + virtual ~SvNumberFormatterRegistry_Impl() override; + + void Insert( SvNumberFormatter* pThis ) + { aFormatters.push_back( pThis ); } + + void Remove( SvNumberFormatter const * pThis ); + + size_t Count() const + { return aFormatters.size(); } + + virtual void ConfigurationChanged( utl::ConfigurationBroadcaster*, ConfigurationHints ) override; +}; + +SvNumberFormatterRegistry_Impl::SvNumberFormatterRegistry_Impl() + : eSysLanguage(MsLangId::getRealLanguage( LANGUAGE_SYSTEM )) +{ + aSysLocaleOptions.AddListener( this ); +} + + +SvNumberFormatterRegistry_Impl::~SvNumberFormatterRegistry_Impl() +{ + aSysLocaleOptions.RemoveListener( this ); +} + + +void SvNumberFormatterRegistry_Impl::Remove( SvNumberFormatter const * pThis ) +{ + auto it = std::find(aFormatters.begin(), aFormatters.end(), pThis); + if (it != aFormatters.end()) + aFormatters.erase( it ); +} + +void SvNumberFormatterRegistry_Impl::ConfigurationChanged( utl::ConfigurationBroadcaster*, + ConfigurationHints nHint) +{ + ::osl::MutexGuard aGuard( SvNumberFormatter::GetGlobalMutex() ); + + if ( nHint & ConfigurationHints::Locale ) + { + for(SvNumberFormatter* pFormatter : aFormatters) + pFormatter->ReplaceSystemCL( eSysLanguage ); + eSysLanguage = MsLangId::getRealLanguage( LANGUAGE_SYSTEM ); + } + if ( nHint & ConfigurationHints::Currency ) + { + for(SvNumberFormatter* pFormatter : aFormatters) + pFormatter->ResetDefaultSystemCurrency(); + } + if ( nHint & ConfigurationHints::DatePatterns ) + { + for(SvNumberFormatter* pFormatter : aFormatters) + pFormatter->InvalidateDateAcceptancePatterns(); + } +} + + +SvNumberFormatterRegistry_Impl* SvNumberFormatter::pFormatterRegistry = nullptr; +volatile bool SvNumberFormatter::bCurrencyTableInitialized = false; +namespace +{ + NfCurrencyTable& theCurrencyTable() + { + static NfCurrencyTable SINGLETON; + return SINGLETON; + } + + NfCurrencyTable& theLegacyOnlyCurrencyTable() + { + static NfCurrencyTable SINGLETON; + return SINGLETON; + } + + /** THE set of installed locales. */ + std::set< LanguageType > theInstalledLocales; + +} +sal_uInt16 SvNumberFormatter::nSystemCurrencyPosition = 0; + +// Whether BankSymbol (not CurrencySymbol!) is always at the end (1 $;-1 $) or +// language dependent. +#define NF_BANKSYMBOL_FIX_POSITION 1 + +const sal_uInt16 SvNumberFormatter::UNLIMITED_PRECISION = ::std::numeric_limits::max(); +const sal_uInt16 SvNumberFormatter::INPUTSTRING_PRECISION = ::std::numeric_limits::max()-1; + +SvNumberFormatter::SvNumberFormatter( const Reference< XComponentContext >& rxContext, + LanguageType eLang ) + : m_xContext( rxContext ) + , maLanguageTag( eLang) +{ + ImpConstruct( eLang ); +} + +SvNumberFormatter::~SvNumberFormatter() +{ + { + ::osl::MutexGuard aGuard( GetGlobalMutex() ); + pFormatterRegistry->Remove( this ); + if ( !pFormatterRegistry->Count() ) + { + delete pFormatterRegistry; + pFormatterRegistry = nullptr; + } + } + + aFTable.clear(); + ClearMergeTable(); +} + + +void SvNumberFormatter::ImpConstruct( LanguageType eLang ) +{ + if ( eLang == LANGUAGE_DONTKNOW ) + { + eLang = UNKNOWN_SUBSTITUTE; + } + IniLnge = eLang; + ActLnge = eLang; + eEvalDateFormat = NF_EVALDATEFORMAT_INTL; + nDefaultSystemCurrencyFormat = NUMBERFORMAT_ENTRY_NOT_FOUND; + + maLanguageTag.reset( eLang ); + xCharClass.changeLocale( m_xContext, maLanguageTag ); + xLocaleData.init( m_xContext, maLanguageTag ); + xCalendar.init( m_xContext, maLanguageTag.getLocale() ); + xTransliteration.init( m_xContext, eLang ); + xNatNum.init( m_xContext ); + + // cached locale data items + const LocaleDataWrapper* pLoc = GetLocaleData(); + aDecimalSep = pLoc->getNumDecimalSep(); + aDecimalSepAlt = pLoc->getNumDecimalSepAlt(); + aThousandSep = pLoc->getNumThousandSep(); + aDateSep = pLoc->getDateSep(); + + pStringScanner.reset( new ImpSvNumberInputScan( this ) ); + pFormatScanner.reset( new ImpSvNumberformatScan( this ) ); + pFormatTable = nullptr; + MaxCLOffset = 0; + ImpGenerateFormats( 0, false ); // 0 .. 999 for initialized language formats + pMergeTable = nullptr; + bNoZero = false; + + ::osl::MutexGuard aGuard( GetGlobalMutex() ); + GetFormatterRegistry().Insert( this ); +} + + +void SvNumberFormatter::ChangeIntl(LanguageType eLnge) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if (ActLnge == eLnge) + return; + + ActLnge = eLnge; + + maLanguageTag.reset( eLnge ); + xCharClass.changeLocale( m_xContext, maLanguageTag ); + xLocaleData.changeLocale( maLanguageTag ); + xCalendar.changeLocale( maLanguageTag.getLocale() ); + xTransliteration.changeLocale( eLnge ); + + // cached locale data items, initialize BEFORE calling ChangeIntl below + const LocaleDataWrapper* pLoc = GetLocaleData(); + aDecimalSep = pLoc->getNumDecimalSep(); + aDecimalSepAlt = pLoc->getNumDecimalSepAlt(); + aThousandSep = pLoc->getNumThousandSep(); + aDateSep = pLoc->getDateSep(); + + pFormatScanner->ChangeIntl(); + pStringScanner->ChangeIntl(); +} + + +// static +::osl::Mutex& SvNumberFormatter::GetGlobalMutex() +{ + // #i77768# Due to a static reference in the toolkit lib + // we need a mutex that lives longer than the svl library. + // Otherwise the dtor would use a destructed mutex!! + static osl::Mutex* persistentMutex(new osl::Mutex); + + return *persistentMutex; +} + + +// static +SvNumberFormatterRegistry_Impl& SvNumberFormatter::GetFormatterRegistry() +{ + ::osl::MutexGuard aGuard( GetGlobalMutex() ); + if ( !pFormatterRegistry ) + { + pFormatterRegistry = new SvNumberFormatterRegistry_Impl; + } + return *pFormatterRegistry; +} + +void SvNumberFormatter::SetColorLink( const Link& rColorTableCallBack ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + aColorLink = rColorTableCallBack; +} + +Color* SvNumberFormatter::GetUserDefColor(sal_uInt16 nIndex) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if( aColorLink.IsSet() ) + { + return aColorLink.Call(nIndex); + } + else + { + return nullptr; + } +} + +void SvNumberFormatter::ChangeNullDate(sal_uInt16 nDay, + sal_uInt16 nMonth, + sal_Int16 nYear) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + pFormatScanner->ChangeNullDate(nDay, nMonth, nYear); + pStringScanner->ChangeNullDate(nDay, nMonth, nYear); +} + +const Date& SvNumberFormatter::GetNullDate() const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + return pFormatScanner->GetNullDate(); +} + +void SvNumberFormatter::ChangeStandardPrec(short nPrec) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + pFormatScanner->ChangeStandardPrec(nPrec); +} + +void SvNumberFormatter::SetNoZero(bool bNZ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + bNoZero = bNZ; +} + +sal_uInt16 SvNumberFormatter::GetStandardPrec() const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + return pFormatScanner->GetStandardPrec(); +} + +bool SvNumberFormatter::GetNoZero() const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + return bNoZero; +} + +void SvNumberFormatter::ReplaceSystemCL( LanguageType eOldLanguage ) +{ + sal_uInt32 nCLOffset = ImpGetCLOffset( LANGUAGE_SYSTEM ); + if ( nCLOffset > MaxCLOffset ) + { + return ; // no SYSTEM entries to replace + } + const sal_uInt32 nMaxBuiltin = nCLOffset + SV_MAX_COUNT_STANDARD_FORMATS; + const sal_uInt32 nNextCL = nCLOffset + SV_COUNTRY_LANGUAGE_OFFSET; + sal_uInt32 nKey; + + // remove old builtin formats + auto it = aFTable.find( nCLOffset ); + while ( it != aFTable.end() && (nKey = it->first) >= nCLOffset && nKey <= nMaxBuiltin ) + { + it = aFTable.erase(it); + } + + // move additional and user defined to temporary table + SvNumberFormatTable aOldTable; + while ( it != aFTable.end() && (nKey = it->first) >= nCLOffset && nKey < nNextCL ) + { + aOldTable[ nKey ] = it->second.release(); + it = aFTable.erase(it); + } + + // generate new old builtin formats + // reset ActLnge otherwise ChangeIntl() wouldn't switch if already LANGUAGE_SYSTEM + ActLnge = LANGUAGE_DONTKNOW; + ChangeIntl( LANGUAGE_SYSTEM ); + ImpGenerateFormats( nCLOffset, true ); + + // convert additional and user defined from old system to new system + SvNumberformat* pStdFormat = GetFormatEntry( nCLOffset + ZF_STANDARD ); + sal_uInt32 nLastKey = nMaxBuiltin; + pFormatScanner->SetConvertMode( eOldLanguage, LANGUAGE_SYSTEM, true , true); + while ( !aOldTable.empty() ) + { + nKey = aOldTable.begin()->first; + if ( nLastKey < nKey ) + { + nLastKey = nKey; + } + std::unique_ptr pOldEntry(aOldTable.begin()->second); + aOldTable.erase( nKey ); + OUString aString( pOldEntry->GetFormatstring() ); + + // Same as PutEntry() but assures key position even if format code is + // a duplicate. Also won't mix up any LastInsertKey. + ChangeIntl( eOldLanguage ); + LanguageType eLge = eOldLanguage; // ConvertMode changes this + bool bCheck = false; + sal_Int32 nCheckPos = -1; + std::unique_ptr pNewEntry(new SvNumberformat( aString, pFormatScanner.get(), + pStringScanner.get(), nCheckPos, eLge )); + if ( nCheckPos == 0 ) + { + SvNumFormatType eCheckType = pNewEntry->GetType(); + if ( eCheckType != SvNumFormatType::UNDEFINED ) + { + pNewEntry->SetType( eCheckType | SvNumFormatType::DEFINED ); + } + else + { + pNewEntry->SetType( SvNumFormatType::DEFINED ); + } + + if ( aFTable.emplace( nKey, std::move(pNewEntry) ).second ) + { + bCheck = true; + } + } + DBG_ASSERT( bCheck, "SvNumberFormatter::ReplaceSystemCL: couldn't convert" ); + } + pFormatScanner->SetConvertMode(false); + pStdFormat->SetLastInsertKey( sal_uInt16(nLastKey - nCLOffset), SvNumberformat::FormatterPrivateAccess() ); + + // append new system additional formats + css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext ); + ImpGenerateAdditionalFormats( nCLOffset, xNFC, true ); +} + +const css::uno::Reference& SvNumberFormatter::GetComponentContext() const +{ + return m_xContext; +} + +const ImpSvNumberformatScan* SvNumberFormatter::GetFormatScanner() const { return pFormatScanner.get(); } + +const LanguageTag& SvNumberFormatter::GetLanguageTag() const { return maLanguageTag; } + +const ::utl::TransliterationWrapper* SvNumberFormatter::GetTransliteration() const +{ + return xTransliteration.get(); +} + +const CharClass* SvNumberFormatter::GetCharClass() const { return xCharClass.get(); } + +const LocaleDataWrapper* SvNumberFormatter::GetLocaleData() const { return xLocaleData.get(); } + +CalendarWrapper* SvNumberFormatter::GetCalendar() const { return xCalendar.get(); } + +const NativeNumberWrapper* SvNumberFormatter::GetNatNum() const { return xNatNum.get(); } + +const OUString& SvNumberFormatter::GetNumDecimalSep() const { return aDecimalSep; } + +const OUString& SvNumberFormatter::GetNumDecimalSepAlt() const { return aDecimalSepAlt; } + +const OUString& SvNumberFormatter::GetNumThousandSep() const { return aThousandSep; } + +const OUString& SvNumberFormatter::GetDateSep() const { return aDateSep; } + +bool SvNumberFormatter::IsDecimalSep( std::u16string_view rStr ) const +{ + if (rStr == GetNumDecimalSep()) + return true; + if (GetNumDecimalSepAlt().isEmpty()) + return false; + return rStr == GetNumDecimalSepAlt(); +} + +bool SvNumberFormatter::IsTextFormat(sal_uInt32 F_Index) const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + const SvNumberformat* pFormat = GetFormatEntry(F_Index); + + return pFormat && pFormat->IsTextFormat(); +} + +bool SvNumberFormatter::PutEntry(OUString& rString, + sal_Int32& nCheckPos, + SvNumFormatType& nType, + sal_uInt32& nKey, // format key + LanguageType eLnge, + bool bReplaceBooleanEquivalent) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + nKey = 0; + if (rString.isEmpty()) // empty string + { + nCheckPos = 1; // -> Error + return false; + } + if (eLnge == LANGUAGE_DONTKNOW) + { + eLnge = IniLnge; + } + ChangeIntl(eLnge); // change locale if necessary + LanguageType eLge = eLnge; // non-const for ConvertMode + bool bCheck = false; + std::unique_ptr p_Entry(new SvNumberformat(rString, + pFormatScanner.get(), + pStringScanner.get(), + nCheckPos, + eLge, + bReplaceBooleanEquivalent)); + + if (nCheckPos == 0) // Format ok + { // Type comparison: + SvNumFormatType eCheckType = p_Entry->GetType(); + if ( eCheckType != SvNumFormatType::UNDEFINED) + { + p_Entry->SetType(eCheckType | SvNumFormatType::DEFINED); + nType = eCheckType; + } + else + { + p_Entry->SetType(SvNumFormatType::DEFINED); + nType = SvNumFormatType::DEFINED; + } + + sal_uInt32 CLOffset = ImpGenerateCL(eLge); // create new standard formats if necessary + + nKey = ImpIsEntry(p_Entry->GetFormatstring(),CLOffset, eLge); + if (nKey == NUMBERFORMAT_ENTRY_NOT_FOUND) // only in not yet present + { + SvNumberformat* pStdFormat = GetFormatEntry(CLOffset + ZF_STANDARD); + sal_uInt32 nPos = CLOffset + pStdFormat->GetLastInsertKey( SvNumberformat::FormatterPrivateAccess() ); + if (nPos+1 - CLOffset >= SV_COUNTRY_LANGUAGE_OFFSET) + { + SAL_WARN( "svl.numbers", "SvNumberFormatter::PutEntry: too many formats for CL"); + } + else if (!aFTable.emplace( nPos+1, std::move(p_Entry)).second) + { + SAL_WARN( "svl.numbers", "SvNumberFormatter::PutEntry: dup position"); + } + else + { + bCheck = true; + nKey = nPos+1; + pStdFormat->SetLastInsertKey(static_cast(nKey-CLOffset), SvNumberformat::FormatterPrivateAccess()); + } + } + } + return bCheck; +} + +bool SvNumberFormatter::PutandConvertEntry(OUString& rString, + sal_Int32& nCheckPos, + SvNumFormatType& nType, + sal_uInt32& nKey, + LanguageType eLnge, + LanguageType eNewLnge, + bool bConvertDateOrder, + bool bReplaceBooleanEquivalent ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + bool bRes; + if (eNewLnge == LANGUAGE_DONTKNOW) + { + eNewLnge = IniLnge; + } + pFormatScanner->SetConvertMode(eLnge, eNewLnge, false, bConvertDateOrder); + bRes = PutEntry(rString, nCheckPos, nType, nKey, eLnge, bReplaceBooleanEquivalent); + pFormatScanner->SetConvertMode(false); + + if (bReplaceBooleanEquivalent && nCheckPos == 0 && nType == SvNumFormatType::DEFINED + && nKey != NUMBERFORMAT_ENTRY_NOT_FOUND) + { + // The boolean string formats are always "user defined" without any + // other type. + const SvNumberformat* pEntry = GetFormatEntry(nKey); + if (pEntry && pEntry->GetType() == SvNumFormatType::DEFINED) + { + // Replace boolean string format with Boolean in target locale, in + // case the source strings are the target locale's. + const OUString aSaveString = rString; + ChangeIntl(eNewLnge); + if (pFormatScanner->ReplaceBooleanEquivalent( rString)) + { + const sal_Int32 nSaveCheckPos = nCheckPos; + const SvNumFormatType nSaveType = nType; + const sal_uInt32 nSaveKey = nKey; + const bool bTargetRes = PutEntry(rString, nCheckPos, nType, nKey, eNewLnge, false); + if (nCheckPos == 0 && nType == SvNumFormatType::LOGICAL && nKey != NUMBERFORMAT_ENTRY_NOT_FOUND) + { + bRes = bTargetRes; + } + else + { + SAL_WARN("svl.numbers", "SvNumberFormatter::PutandConvertEntry: can't scan boolean replacement"); + // Live with the source boolean string format. + rString = aSaveString; + nCheckPos = nSaveCheckPos; + nType = nSaveType; + nKey = nSaveKey; + } + } + } + } + return bRes; +} + +bool SvNumberFormatter::PutandConvertEntrySystem(OUString& rString, + sal_Int32& nCheckPos, + SvNumFormatType& nType, + sal_uInt32& nKey, + LanguageType eLnge, + LanguageType eNewLnge) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + bool bRes; + if (eNewLnge == LANGUAGE_DONTKNOW) + { + eNewLnge = IniLnge; + } + pFormatScanner->SetConvertMode(eLnge, eNewLnge, true, true); + bRes = PutEntry(rString, nCheckPos, nType, nKey, eLnge); + pFormatScanner->SetConvertMode(false); + return bRes; +} + +sal_uInt32 SvNumberFormatter::GetIndexPuttingAndConverting( OUString & rString, LanguageType eLnge, + LanguageType eSysLnge, SvNumFormatType & rType, + bool & rNewInserted, sal_Int32 & rCheckPos ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + sal_uInt32 nKey = NUMBERFORMAT_ENTRY_NOT_FOUND; + rNewInserted = false; + rCheckPos = 0; + + // #62389# empty format string (of Writer) => General standard format + if (rString.isEmpty()) + { + // nothing + } + else if (eLnge == LANGUAGE_SYSTEM && eSysLnge != SvtSysLocale().GetLanguageTag().getLanguageType()) + { + sal_uInt32 nOrig = GetEntryKey( rString, eSysLnge ); + if (nOrig == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + nKey = nOrig; // none available, maybe user-defined + } + else + { + nKey = GetFormatForLanguageIfBuiltIn( nOrig, SvtSysLocale().GetLanguageTag().getLanguageType() ); + } + if (nKey == nOrig) + { + // Not a builtin format, convert. + // The format code string may get modified and adapted to the real + // language and wouldn't match eSysLnge anymore, do that on a copy. + OUString aTmp( rString); + rNewInserted = PutandConvertEntrySystem( aTmp, rCheckPos, rType, + nKey, eLnge, SvtSysLocale().GetLanguageTag().getLanguageType()); + if (rCheckPos > 0) + { + SAL_WARN( "svl.numbers", "SvNumberFormatter::GetIndexPuttingAndConverting: bad format code string for current locale"); + nKey = NUMBERFORMAT_ENTRY_NOT_FOUND; + } + } + } + else + { + nKey = GetEntryKey( rString, eLnge); + if (nKey == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + rNewInserted = PutEntry( rString, rCheckPos, rType, nKey, eLnge); + if (rCheckPos > 0) + { + SAL_WARN( "svl.numbers", "SvNumberFormatter::GetIndexPuttingAndConverting: bad format code string for specified locale"); + nKey = NUMBERFORMAT_ENTRY_NOT_FOUND; + } + } + } + if (nKey == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + nKey = GetStandardIndex( eLnge); + } + rType = GetType( nKey); + // Convert any (!) old "automatic" currency format to new fixed currency + // default format. + if (rType & SvNumFormatType::CURRENCY) + { + const SvNumberformat* pFormat = GetEntry( nKey); + if (!pFormat->HasNewCurrency()) + { + if (rNewInserted) + { + DeleteEntry( nKey); // don't leave trails of rubbish + rNewInserted = false; + } + nKey = GetStandardFormat( SvNumFormatType::CURRENCY, eLnge); + } + } + return nKey; +} + +void SvNumberFormatter::DeleteEntry(sal_uInt32 nKey) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + aFTable.erase(nKey); +} + +void SvNumberFormatter::GetUsedLanguages( std::vector& rList ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + rList.clear(); + + sal_uInt32 nOffset = 0; + while (nOffset <= MaxCLOffset) + { + SvNumberformat* pFormat = GetFormatEntry(nOffset); + if (pFormat) + { + rList.push_back( pFormat->GetLanguage() ); + } + nOffset += SV_COUNTRY_LANGUAGE_OFFSET; + } +} + + +void SvNumberFormatter::FillKeywordTable( NfKeywordTable& rKeywords, + LanguageType eLang ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + ChangeIntl( eLang ); + const NfKeywordTable & rTable = pFormatScanner->GetKeywords(); + for ( sal_uInt16 i = 0; i < NF_KEYWORD_ENTRIES_COUNT; ++i ) + { + rKeywords[i] = rTable[i]; + } +} + + +void SvNumberFormatter::FillKeywordTableForExcel( NfKeywordTable& rKeywords ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + FillKeywordTable( rKeywords, LANGUAGE_ENGLISH_US ); + + // Replace upper case "GENERAL" with proper case "General". + rKeywords[ NF_KEY_GENERAL ] = GetStandardName( LANGUAGE_ENGLISH_US ); + + // Excel or OOXML do not specify format code keywords case sensitivity, + // but given and writes them lower case. Using upper case even lead to an + // odd misrepresentation in iOS viewer and OSX Quicklook viewer that + // strangely use "D" and "DD" for "days since beginning of year", which is + // nowhere defined. See tdf#126773 + // Use lower case for all date and time keywords where known. See OOXML + // ECMA-376-1:2016 18.8.31 numFmts (Number Formats) + rKeywords[ NF_KEY_MI ] = "m"; + rKeywords[ NF_KEY_MMI ] = "mm"; + rKeywords[ NF_KEY_M ] = "m"; + rKeywords[ NF_KEY_MM ] = "mm"; + rKeywords[ NF_KEY_MMM ] = "mmm"; + rKeywords[ NF_KEY_MMMM ] = "mmmm"; + rKeywords[ NF_KEY_MMMMM ] = "mmmmm"; + rKeywords[ NF_KEY_H ] = "h"; + rKeywords[ NF_KEY_HH ] = "hh"; + rKeywords[ NF_KEY_S ] = "s"; + rKeywords[ NF_KEY_SS ] = "ss"; + /* XXX: not defined in OOXML: rKeywords[ NF_KEY_Q ] = "q"; */ + /* XXX: not defined in OOXML: rKeywords[ NF_KEY_QQ ] = "qq"; */ + rKeywords[ NF_KEY_D ] = "d"; + rKeywords[ NF_KEY_DD ] = "dd"; + rKeywords[ NF_KEY_DDD ] = "ddd"; + rKeywords[ NF_KEY_DDDD ] = "dddd"; + rKeywords[ NF_KEY_YY ] = "yy"; + rKeywords[ NF_KEY_YYYY ] = "yyyy"; + /* XXX: not defined in OOXML: rKeywords[ NF_KEY_AAA ] = "aaa"; */ + /* XXX: not defined in OOXML: rKeywords[ NF_KEY_AAAA ] = "aaaa"; */ + rKeywords[ NF_KEY_EC ] = "e"; + rKeywords[ NF_KEY_EEC ] = "ee"; + rKeywords[ NF_KEY_G ] = "g"; + rKeywords[ NF_KEY_GG ] = "gg"; + rKeywords[ NF_KEY_GGG ] = "ggg"; + rKeywords[ NF_KEY_R ] = "r"; + rKeywords[ NF_KEY_RR ] = "rr"; + /* XXX: not defined in OOXML: rKeywords[ NF_KEY_WW ] = "ww"; */ + + // Remap codes unknown to Excel. + rKeywords[ NF_KEY_NN ] = "ddd"; + rKeywords[ NF_KEY_NNN ] = "dddd"; + // NNNN gets a separator appended in SvNumberformat::GetMappedFormatString() + rKeywords[ NF_KEY_NNNN ] = "dddd"; + // Export the Thai T NatNum modifier. This must be uppercase for internal reasons. + rKeywords[ NF_KEY_THAI_T ] = "T"; +} + + +static OUString lcl_buildBooleanStringFormat( SvNumberformat* pEntry ) +{ + // Build Boolean number format, which needs non-zero and zero subformat + // codes with TRUE and FALSE strings. + const Color* pColor = nullptr; + OUString aFormatStr, aTemp; + pEntry->GetOutputString( 1.0, aTemp, &pColor ); + aFormatStr += "\"" + aTemp + "\";\"" + aTemp + "\";\""; + pEntry->GetOutputString( 0.0, aTemp, &pColor ); + aFormatStr += aTemp + "\""; + return aFormatStr; +} + + +OUString SvNumberFormatter::GetFormatStringForExcel( sal_uInt32 nKey, const NfKeywordTable& rKeywords, + SvNumberFormatter& rTempFormatter ) const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + OUString aFormatStr; + if (const SvNumberformat* pEntry = GetEntry( nKey)) + { + if (pEntry->GetType() == SvNumFormatType::LOGICAL) + { + // Build a source locale dependent string boolean. This is + // expected when loading the document in the same locale or if + // several locales are used, but not for other system/default + // locales. You can't have both. We could force to English for all + // locales like below, but Excel would display English strings then + // even for the system locale matching this locale. YMMV. + aFormatStr = lcl_buildBooleanStringFormat( const_cast< SvNumberformat* >(pEntry)); + } + else + { + bool bSystemLanguage = false; + LanguageType nLang = pEntry->GetLanguage(); + if (nLang == LANGUAGE_SYSTEM) + { + bSystemLanguage = true; + nLang = SvtSysLocale().GetLanguageTag().getLanguageType(); + } + if (nLang != LANGUAGE_ENGLISH_US) + { + sal_Int32 nCheckPos; + SvNumFormatType nType = SvNumFormatType::DEFINED; + sal_uInt32 nTempKey; + OUString aTemp( pEntry->GetFormatstring()); + /* TODO: do we want bReplaceBooleanEquivalent=true in any case + * to write it as English string boolean? */ + rTempFormatter.PutandConvertEntry( aTemp, nCheckPos, nType, nTempKey, nLang, LANGUAGE_ENGLISH_US, + false /*bConvertDateOrder*/, false /*bReplaceBooleanEquivalent*/); + SAL_WARN_IF( nCheckPos != 0, "svl.numbers", + "SvNumberFormatter::GetFormatStringForExcel - format code not convertible"); + if (nTempKey != NUMBERFORMAT_ENTRY_NOT_FOUND) + pEntry = rTempFormatter.GetEntry( nTempKey); + } + + if (pEntry) + { + if (pEntry->GetType() == SvNumFormatType::LOGICAL) + { + // This would be reached if bReplaceBooleanEquivalent was + // true and the source format is a string boolean like + // >"VRAI";"VRAI";"FAUX"< recognized as real boolean and + // properly converted. Then written as + // >"TRUE";"TRUE";"FALSE"< + aFormatStr = lcl_buildBooleanStringFormat( const_cast< SvNumberformat* >(pEntry)); + } + else + { + // GetLocaleData() returns the current locale's data, so switch + // before (which doesn't do anything if it was the same locale + // already). + rTempFormatter.ChangeIntl( LANGUAGE_ENGLISH_US); + aFormatStr = pEntry->GetMappedFormatstring( rKeywords, *rTempFormatter.GetLocaleData(), nLang, + bSystemLanguage); + } + } + } + } + else + { + SAL_WARN("svl.numbers","SvNumberFormatter::GetFormatStringForExcel - format not found: " << nKey); + } + + if (aFormatStr.isEmpty()) + aFormatStr = "General"; + return aFormatStr; +} + + +OUString SvNumberFormatter::GetKeyword( LanguageType eLnge, sal_uInt16 nIndex ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + ChangeIntl(eLnge); + const NfKeywordTable & rTable = pFormatScanner->GetKeywords(); + if ( nIndex < NF_KEYWORD_ENTRIES_COUNT ) + { + return rTable[nIndex]; + } + SAL_WARN( "svl.numbers", "GetKeyword: invalid index"); + return OUString(); +} + + +OUString SvNumberFormatter::GetStandardName( LanguageType eLnge ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + ChangeIntl( eLnge ); + return pFormatScanner->GetStandardName(); +} + + +sal_uInt32 SvNumberFormatter::ImpGetCLOffset(LanguageType eLnge) const +{ + sal_uInt32 nOffset = 0; + while (nOffset <= MaxCLOffset) + { + const SvNumberformat* pFormat = GetFormatEntry(nOffset); + if (pFormat && pFormat->GetLanguage() == eLnge) + { + return nOffset; + } + nOffset += SV_COUNTRY_LANGUAGE_OFFSET; + } + return nOffset; +} + +sal_uInt32 SvNumberFormatter::ImpIsEntry(std::u16string_view rString, + sal_uInt32 nCLOffset, + LanguageType eLnge) +{ + sal_uInt32 res = NUMBERFORMAT_ENTRY_NOT_FOUND; + auto it = aFTable.find( nCLOffset); + while ( res == NUMBERFORMAT_ENTRY_NOT_FOUND && + it != aFTable.end() && it->second->GetLanguage() == eLnge ) + { + if ( rString == it->second->GetFormatstring() ) + { + res = it->first; + } + else + { + ++it; + } + } + return res; +} + + +SvNumberFormatTable& SvNumberFormatter::GetFirstEntryTable( + SvNumFormatType& eType, + sal_uInt32& FIndex, + LanguageType& rLnge) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + SvNumFormatType eTypetmp = eType; + if (eType == SvNumFormatType::ALL) // empty cell or don't care + { + rLnge = IniLnge; + } + else + { + SvNumberformat* pFormat = GetFormatEntry(FIndex); + if (!pFormat) + { + rLnge = IniLnge; + eType = SvNumFormatType::ALL; + eTypetmp = eType; + } + else + { + rLnge = pFormat->GetLanguage(); + eType = pFormat->GetMaskedType(); + if (eType == SvNumFormatType::ALL) + { + eType = SvNumFormatType::DEFINED; + eTypetmp = eType; + } + else if (eType == SvNumFormatType::DATETIME) + { + eTypetmp = eType; + eType = SvNumFormatType::DATE; + } + else + { + eTypetmp = eType; + } + } + } + ChangeIntl(rLnge); + return GetEntryTable(eTypetmp, FIndex, rLnge); +} + +sal_uInt32 SvNumberFormatter::ImpGenerateCL( LanguageType eLnge ) +{ + ChangeIntl(eLnge); + sal_uInt32 CLOffset = ImpGetCLOffset(ActLnge); + if (CLOffset > MaxCLOffset) + { + // new CL combination + if (LocaleDataWrapper::areChecksEnabled()) + { + const LanguageTag& rLoadedLocale = xLocaleData->getLoadedLanguageTag(); + if ( !rLoadedLocale.equals( maLanguageTag ) ) + { + LocaleDataWrapper::outputCheckMessage( xLocaleData->appendLocaleInfo( u"SvNumberFormatter::ImpGenerateCL: locales don't match:" )); + } + // test XML locale data FormatElement entries + { + uno::Sequence< i18n::FormatElement > xSeq = xLocaleData->getAllFormats(); + // A test for completeness of formatindex="0" ... + // formatindex="47" is not needed here since it is done in + // ImpGenerateFormats(). + + // Test for dupes of formatindex="..." + for ( sal_Int32 j = 0; j < xSeq.getLength(); j++ ) + { + sal_Int16 nIdx = xSeq[j].formatIndex; + OUStringBuffer aDupes; + for ( sal_Int32 i = 0; i < xSeq.getLength(); i++ ) + { + if ( i != j && xSeq[i].formatIndex == nIdx ) + { + aDupes.append( i ); + aDupes.append("("); + aDupes.append(xSeq[i].formatKey); + aDupes.append( ") "); + } + } + if ( !aDupes.isEmpty() ) + { + OUString aMsg = "XML locale data FormatElement formatindex dupe: " + + OUString::number(nIdx) + + "\nFormatElements: " + + OUString::number( j ) + + "(" + + xSeq[j].formatKey + + ") " + + aDupes.makeStringAndClear(); + LocaleDataWrapper::outputCheckMessage( xLocaleData->appendLocaleInfo( aMsg )); + } + } + } + } + + MaxCLOffset += SV_COUNTRY_LANGUAGE_OFFSET; + ImpGenerateFormats( MaxCLOffset, false/*bNoAdditionalFormats*/ ); + CLOffset = MaxCLOffset; + } + return CLOffset; +} + +SvNumberFormatTable& SvNumberFormatter::ChangeCL(SvNumFormatType eType, + sal_uInt32& FIndex, + LanguageType eLnge) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + ImpGenerateCL(eLnge); + return GetEntryTable(eType, FIndex, ActLnge); +} + +SvNumberFormatTable& SvNumberFormatter::GetEntryTable( + SvNumFormatType eType, + sal_uInt32& FIndex, + LanguageType eLnge) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if ( pFormatTable ) + { + pFormatTable->clear(); + } + else + { + pFormatTable.reset( new SvNumberFormatTable ); + } + ChangeIntl(eLnge); + sal_uInt32 CLOffset = ImpGetCLOffset(ActLnge); + + // Might generate and insert a default format for the given type + // (e.g. currency) => has to be done before collecting formats. + sal_uInt32 nDefaultIndex = GetStandardFormat( eType, ActLnge ); + + auto it = aFTable.find( CLOffset); + + if (eType == SvNumFormatType::ALL) + { + while (it != aFTable.end() && it->second->GetLanguage() == ActLnge) + { // copy all entries to output table + (*pFormatTable)[ it->first ] = it->second.get(); + ++it; + } + } + else + { + while (it != aFTable.end() && it->second->GetLanguage() == ActLnge) + { // copy entries of queried type to output table + if ((it->second->GetType()) & eType) + (*pFormatTable)[ it->first ] = it->second.get(); + ++it; + } + } + if ( !pFormatTable->empty() ) + { // select default if queried format doesn't exist or queried type or + // language differ from existing format + SvNumberformat* pEntry = GetFormatEntry(FIndex); + if ( !pEntry || !(pEntry->GetType() & eType) || pEntry->GetLanguage() != ActLnge ) + { + FIndex = nDefaultIndex; + } + } + return *pFormatTable; +} + +bool SvNumberFormatter::IsNumberFormat(const OUString& sString, + sal_uInt32& F_Index, + double& fOutNumber, + SvNumInputOptions eInputOptions) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + + SvNumFormatType FType; + // For the 0 General format directly use the init/system locale and avoid + // all overhead that is associated with a format passed to the scanner. + const SvNumberformat* pFormat = (F_Index == 0 ? nullptr : ImpSubstituteEntry( GetFormatEntry(F_Index))); + if (!pFormat) + { + ChangeIntl(IniLnge); + FType = SvNumFormatType::NUMBER; + } + else + { + FType = pFormat->GetMaskedType(); + if (FType == SvNumFormatType::ALL) + { + FType = SvNumFormatType::DEFINED; + } + ChangeIntl(pFormat->GetLanguage()); + // Avoid scanner overhead with the General format of any locale. + // These are never substituted above so safe to ignore. + if ((F_Index % SV_COUNTRY_LANGUAGE_OFFSET) == 0) + { + assert(FType == SvNumFormatType::NUMBER); + pFormat = nullptr; + } + } + + bool res; + SvNumFormatType RType = FType; + if (RType == SvNumFormatType::TEXT) + { + res = false; // type text preset => no conversion to number + } + else + { + res = pStringScanner->IsNumberFormat(sString, RType, fOutNumber, pFormat, eInputOptions); + } + if (res && !IsCompatible(FType, RType)) // non-matching type + { + switch ( RType ) + { + case SvNumFormatType::DATE : + // Preserve ISO 8601 input. + if (pStringScanner->CanForceToIso8601( DateOrder::Invalid)) + { + F_Index = GetFormatIndex( NF_DATE_DIN_YYYYMMDD, ActLnge ); + } + else + { + F_Index = GetStandardFormat( RType, ActLnge ); + } + break; + case SvNumFormatType::TIME : + if ( pStringScanner->GetDecPos() ) + { + // 100th seconds + if ( pStringScanner->GetNumericsCount() > 3 || fOutNumber < 0.0 ) + { + F_Index = GetFormatIndex( NF_TIME_HH_MMSS00, ActLnge ); + } + else + { + F_Index = GetFormatIndex( NF_TIME_MMSS00, ActLnge ); + } + } + else if ( fOutNumber >= 1.0 || fOutNumber < 0.0 ) + { + F_Index = GetFormatIndex( NF_TIME_HH_MMSS, ActLnge ); + } + else + { + F_Index = GetStandardFormat( RType, ActLnge ); + } + break; + case SvNumFormatType::DATETIME : + // Preserve ISO 8601 input. + if (pStringScanner->HasIso8601Tsep()) + { + if (pStringScanner->GetDecPos()) + F_Index = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS000, ActLnge ); + else + F_Index = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS, ActLnge ); + } + else if (pStringScanner->CanForceToIso8601( DateOrder::Invalid)) + { + if (pStringScanner->GetDecPos()) + F_Index = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS000, ActLnge ); + else + F_Index = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS, ActLnge ); + } + else + { + F_Index = GetStandardFormat( RType, ActLnge ); + } + break; + default: + F_Index = GetStandardFormat( RType, ActLnge ); + } + } + return res; +} + +LanguageType SvNumberFormatter::GetLanguage() const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + return IniLnge; +} + +// static +bool SvNumberFormatter::IsCompatible(SvNumFormatType eOldType, SvNumFormatType eNewType) +{ + if (eOldType == eNewType) + { + return true; + } + else if (eOldType == SvNumFormatType::DEFINED) + { + return true; + } + else + { + switch (eNewType) + { + case SvNumFormatType::NUMBER: + switch (eOldType) + { + case SvNumFormatType::PERCENT: + case SvNumFormatType::CURRENCY: + case SvNumFormatType::SCIENTIFIC: + case SvNumFormatType::FRACTION: + case SvNumFormatType::DEFINED: + return true; + case SvNumFormatType::LOGICAL: + default: + return false; + } + break; + case SvNumFormatType::DATE: + switch (eOldType) + { + case SvNumFormatType::DATETIME: + return true; + default: + return false; + } + break; + case SvNumFormatType::TIME: + switch (eOldType) + { + case SvNumFormatType::DATETIME: + return true; + default: + return false; + } + break; + case SvNumFormatType::DATETIME: + switch (eOldType) + { + case SvNumFormatType::TIME: + case SvNumFormatType::DATE: + return true; + default: + return false; + } + break; + case SvNumFormatType::DURATION: + return false; + default: + return false; + } + } +} + + +sal_uInt32 SvNumberFormatter::ImpGetDefaultFormat( SvNumFormatType nType ) +{ + sal_uInt32 CLOffset = ImpGetCLOffset( ActLnge ); + sal_uInt32 nSearch; + switch( nType ) + { + case SvNumFormatType::DATE: + nSearch = CLOffset + ZF_STANDARD_DATE; + break; + case SvNumFormatType::TIME: + nSearch = CLOffset + ZF_STANDARD_TIME; + break; + case SvNumFormatType::DATETIME: + nSearch = CLOffset + ZF_STANDARD_DATETIME; + break; + case SvNumFormatType::DURATION: + nSearch = CLOffset + ZF_STANDARD_DURATION; + break; + case SvNumFormatType::PERCENT: + nSearch = CLOffset + ZF_STANDARD_PERCENT; + break; + case SvNumFormatType::SCIENTIFIC: + nSearch = CLOffset + ZF_STANDARD_SCIENTIFIC; + break; + default: + nSearch = CLOffset + ZF_STANDARD; + } + + DefaultFormatKeysMap::const_iterator it = aDefaultFormatKeys.find( nSearch); + sal_uInt32 nDefaultFormat = (it != aDefaultFormatKeys.end() ? + it->second : NUMBERFORMAT_ENTRY_NOT_FOUND); + if ( nDefaultFormat == NUMBERFORMAT_ENTRY_NOT_FOUND ) + { + // look for a defined standard + sal_uInt32 nStopKey = CLOffset + SV_COUNTRY_LANGUAGE_OFFSET; + sal_uInt32 nKey(0); + auto it2 = aFTable.find( CLOffset ); + while ( it2 != aFTable.end() && (nKey = it2->first ) >= CLOffset && nKey < nStopKey ) + { + const SvNumberformat* pEntry = it2->second.get(); + if ( pEntry->IsStandard() && (pEntry->GetMaskedType() == nType) ) + { + nDefaultFormat = nKey; + break; // while + } + ++it2; + } + + if ( nDefaultFormat == NUMBERFORMAT_ENTRY_NOT_FOUND ) + { // none found, use old fixed standards + switch( nType ) + { + case SvNumFormatType::DATE: + nDefaultFormat = CLOffset + ZF_STANDARD_DATE; + break; + case SvNumFormatType::TIME: + nDefaultFormat = CLOffset + ZF_STANDARD_TIME+1; + break; + case SvNumFormatType::DATETIME: + nDefaultFormat = CLOffset + ZF_STANDARD_DATETIME; + break; + case SvNumFormatType::DURATION: + nDefaultFormat = CLOffset + ZF_STANDARD_DURATION; + break; + case SvNumFormatType::PERCENT: + nDefaultFormat = CLOffset + ZF_STANDARD_PERCENT+1; + break; + case SvNumFormatType::SCIENTIFIC: + nDefaultFormat = CLOffset + ZF_STANDARD_SCIENTIFIC; + break; + default: + nDefaultFormat = CLOffset + ZF_STANDARD; + } + } + aDefaultFormatKeys[ nSearch ] = nDefaultFormat; + } + return nDefaultFormat; +} + + +sal_uInt32 SvNumberFormatter::GetStandardFormat( SvNumFormatType eType, LanguageType eLnge ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if (eLnge == LANGUAGE_DONTKNOW) + { + eLnge = IniLnge; + } + sal_uInt32 CLOffset = ImpGenerateCL(eLnge); + switch(eType) + { + case SvNumFormatType::CURRENCY: + return ( eLnge == LANGUAGE_SYSTEM ) ? ImpGetDefaultSystemCurrencyFormat() : ImpGetDefaultCurrencyFormat(); + case SvNumFormatType::DURATION : + return GetFormatIndex( NF_TIME_HH_MMSS, eLnge); + case SvNumFormatType::DATE: + case SvNumFormatType::TIME: + case SvNumFormatType::DATETIME: + case SvNumFormatType::PERCENT: + case SvNumFormatType::SCIENTIFIC: + return ImpGetDefaultFormat( eType ); + case SvNumFormatType::FRACTION: + return CLOffset + ZF_STANDARD_FRACTION; + case SvNumFormatType::LOGICAL: + return CLOffset + ZF_STANDARD_LOGICAL; + case SvNumFormatType::TEXT: + return CLOffset + ZF_STANDARD_TEXT; + case SvNumFormatType::ALL: + case SvNumFormatType::DEFINED: + case SvNumFormatType::NUMBER: + case SvNumFormatType::UNDEFINED: + default: + return CLOffset + ZF_STANDARD; + } +} + +bool SvNumberFormatter::IsSpecialStandardFormat( sal_uInt32 nFIndex, + LanguageType eLnge ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + return + nFIndex == GetFormatIndex( NF_TIME_MMSS00, eLnge ) || + nFIndex == GetFormatIndex( NF_TIME_HH_MMSS00, eLnge ) || + nFIndex == GetFormatIndex( NF_TIME_HH_MMSS, eLnge ) + ; +} + +sal_uInt32 SvNumberFormatter::GetStandardFormat( sal_uInt32 nFIndex, SvNumFormatType eType, + LanguageType eLnge ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if ( IsSpecialStandardFormat( nFIndex, eLnge ) ) + return nFIndex; + else + return GetStandardFormat( eType, eLnge ); +} + +sal_uInt32 SvNumberFormatter::GetTimeFormat( double fNumber, LanguageType eLnge, bool bForceDuration ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + bool bSign; + if ( fNumber < 0.0 ) + { + bSign = true; + fNumber = -fNumber; + } + else + bSign = false; + double fSeconds = fNumber * 86400; + if ( floor( fSeconds + 0.5 ) * 100 != floor( fSeconds * 100 + 0.5 ) ) + { // with 100th seconds + if ( bForceDuration || bSign || fSeconds >= 3600 ) + return GetFormatIndex( NF_TIME_HH_MMSS00, eLnge ); + else + return GetFormatIndex( NF_TIME_MMSS00, eLnge ); + } + else + { + if ( bForceDuration || bSign || fNumber >= 1.0 ) + return GetFormatIndex( NF_TIME_HH_MMSS, eLnge ); + else + return GetStandardFormat( SvNumFormatType::TIME, eLnge ); + } +} + +sal_uInt32 SvNumberFormatter::GetStandardFormat( double fNumber, sal_uInt32 nFIndex, + SvNumFormatType eType, LanguageType eLnge ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if ( IsSpecialStandardFormat( nFIndex, eLnge ) ) + return nFIndex; + + switch( eType ) + { + case SvNumFormatType::DURATION : + return GetTimeFormat( fNumber, eLnge, true); + case SvNumFormatType::TIME : + return GetTimeFormat( fNumber, eLnge, false); + default: + return GetStandardFormat( eType, eLnge ); + } +} + +sal_uInt32 SvNumberFormatter::GuessDateTimeFormat( SvNumFormatType& rType, double fNumber, LanguageType eLnge ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + // Categorize the format according to the implementation of + // SvNumberFormatter::GetEditFormat(), making assumptions about what + // would be time only. + sal_uInt32 nRet; + if (0.0 <= fNumber && fNumber < 1.0) + { + // Clearly a time. + rType = SvNumFormatType::TIME; + nRet = GetTimeFormat( fNumber, eLnge, false); + } + else if (fabs( fNumber) * 24 < 0x7fff) + { + // Assuming duration within 32k hours or 3.7 years. + // This should be SvNumFormatType::DURATION instead, but the outer + // world can't cope with that. + rType = SvNumFormatType::TIME; + nRet = GetTimeFormat( fNumber, eLnge, true); + } + else if (rtl::math::approxFloor( fNumber) != fNumber) + { + // Date+Time. + rType = SvNumFormatType::DATETIME; + nRet = GetFormatIndex( NF_DATETIME_SYS_DDMMYYYY_HHMMSS, eLnge); + } + else + { + // Date only. + rType = SvNumFormatType::DATE; + nRet = GetFormatIndex( NF_DATE_SYS_DDMMYYYY, eLnge); + } + return nRet; +} + +sal_uInt32 SvNumberFormatter::GetEditFormat( double fNumber, sal_uInt32 nFIndex, + SvNumFormatType eType, + SvNumberformat const * pFormat, + LanguageType eForLocale ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + const LanguageType eLang = (pFormat ? pFormat->GetLanguage() : LANGUAGE_SYSTEM); + if (eForLocale == LANGUAGE_DONTKNOW) + eForLocale = eLang; + sal_uInt32 nKey = nFIndex; + switch ( eType ) + { + // #61619# always edit using 4-digit year + case SvNumFormatType::DATE : + { + // Preserve ISO 8601 format. + bool bIsoDate = + nFIndex == GetFormatIndex( NF_DATE_DIN_YYYYMMDD, eLang) || + nFIndex == GetFormatIndex( NF_DATE_DIN_YYMMDD, eLang) || + nFIndex == GetFormatIndex( NF_DATE_DIN_MMDD, eLang) || + (pFormat && pFormat->IsIso8601( 0 )); + if (rtl::math::approxFloor( fNumber) != fNumber) + { + // fdo#34977 preserve time when editing even if only date was + // displayed. + if (bIsoDate) + nKey = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS, eForLocale); + else + nKey = GetFormatIndex( NF_DATETIME_SYS_DDMMYYYY_HHMMSS, eForLocale ); + } + else + { + if (bIsoDate) + nKey = GetFormatIndex( NF_DATE_ISO_YYYYMMDD, eForLocale); + else + nKey = GetFormatIndex( NF_DATE_SYS_DDMMYYYY, eForLocale ); + } + } + break; + case SvNumFormatType::TIME : + if (fNumber < 0.0 || fNumber >= 1.0) + { + /* XXX NOTE: this is a purely arbitrary value within the limits + * of a signed 16-bit. 32k hours are 3.7 years ... or + * 1903-09-26 if date. */ + if (fabs( fNumber) * 24 < 0x7fff) + nKey = GetTimeFormat( fNumber, eForLocale, true); + // Preserve duration, use [HH]:MM:SS instead of time. + else + nKey = GetFormatIndex( NF_DATETIME_SYS_DDMMYYYY_HHMMSS, eForLocale ); + // Assume that a large value is a datetime with only time + // displayed. + } + else + nKey = GetStandardFormat( fNumber, nFIndex, eType, eForLocale ); + break; + case SvNumFormatType::DURATION : + nKey = GetTimeFormat( fNumber, eForLocale, true); + break; + case SvNumFormatType::DATETIME : + if (nFIndex == GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS, eLang)) + nKey = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS, eForLocale ); + else if (nFIndex == GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS000, eLang)) + nKey = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS000, eForLocale ); + else if (nFIndex == GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS000, eLang)) + nKey = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS000, eForLocale ); + else if (nFIndex == GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS, eLang) || (pFormat && pFormat->IsIso8601( 0 ))) + nKey = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS, eForLocale ); + else + nKey = GetFormatIndex( NF_DATETIME_SYS_DDMMYYYY_HHMMSS, eForLocale ); + break; + case SvNumFormatType::NUMBER: + nKey = GetStandardFormat( eType, eForLocale ); + break; + default: + nKey = GetStandardFormat( fNumber, nFIndex, eType, eForLocale ); + } + return nKey; +} + +void SvNumberFormatter::GetInputLineString(const double& fOutNumber, + sal_uInt32 nFIndex, + OUString& sOutString, + bool bFiltering, bool bForceSystemLocale) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + const Color* pColor; + sal_uInt32 nRealKey = nFIndex; + SvNumberformat* pFormat = ImpSubstituteEntry( GetFormatEntry( nFIndex ), &nRealKey); + if (!pFormat) + { + pFormat = GetFormatEntry(ZF_STANDARD); + } + + LanguageType eLang = pFormat->GetLanguage(); + ChangeIntl( eLang ); + + SvNumFormatType eType = pFormat->GetMaskedType(); + if (eType == SvNumFormatType::ALL) + { + // Mixed types in subformats, use first. + /* XXX we could choose a subformat according to fOutNumber and + * subformat conditions, but they may exist to suppress 0 or negative + * numbers so wouldn't be a safe bet. */ + eType = pFormat->GetNumForInfoScannedType(0); + } + const SvNumFormatType eTypeOrig = eType; + + sal_uInt16 nOldPrec = pFormatScanner->GetStandardPrec(); + bool bPrecChanged = false; + if (eType == SvNumFormatType::NUMBER || + eType == SvNumFormatType::PERCENT || + eType == SvNumFormatType::CURRENCY || + eType == SvNumFormatType::SCIENTIFIC || + eType == SvNumFormatType::FRACTION) + { + if (eType != SvNumFormatType::PERCENT) // special treatment of % later + { + eType = SvNumFormatType::NUMBER; + } + ChangeStandardPrec(INPUTSTRING_PRECISION); + bPrecChanged = true; + } + + sal_uInt32 nKey = GetEditFormat( fOutNumber, nRealKey, eType, pFormat, + bForceSystemLocale ? LANGUAGE_SYSTEM : LANGUAGE_DONTKNOW); + // if bFiltering true keep the nRealKey format + if ( nKey != nRealKey && !bFiltering ) + { + pFormat = GetFormatEntry( nKey ); + } + assert(pFormat); + if (pFormat) + { + if ( eType == SvNumFormatType::TIME && pFormat->GetFormatPrecision() ) + { + ChangeStandardPrec(INPUTSTRING_PRECISION); + bPrecChanged = true; + } + pFormat->GetOutputString(fOutNumber, sOutString, &pColor); + + // The #FMT error string must not be used for input as it would lead to + // data loss. This can happen for at least date(+time). Fall back to a + // last resort of plain number in the locale the formatter was + // constructed with. + if (eTypeOrig != SvNumFormatType::NUMBER && sOutString == ImpSvNumberformatScan::sErrStr) + { + pFormat = GetFormatEntry(ZF_STANDARD); + assert(pFormat); + if (pFormat) + { + ChangeStandardPrec(INPUTSTRING_PRECISION); + bPrecChanged = true; + pFormat->GetOutputString(fOutNumber, sOutString, &pColor); + } + } + assert(sOutString != ImpSvNumberformatScan::sErrStr); + } + if (bPrecChanged) + { + ChangeStandardPrec(nOldPrec); + } +} + +void SvNumberFormatter::GetOutputString(const OUString& sString, + sal_uInt32 nFIndex, + OUString& sOutString, + const Color** ppColor, + bool bUseStarFormat ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + SvNumberformat* pFormat = GetFormatEntry( nFIndex ); + // ImpSubstituteEntry() is unnecessary here because so far only numeric + // (time and date) are substituted. + if (!pFormat) + { + pFormat = GetFormatEntry(ZF_STANDARD_TEXT); + } + if (!pFormat->IsTextFormat() && !pFormat->HasTextFormat()) + { + *ppColor = nullptr; + sOutString = sString; + } + else + { + ChangeIntl(pFormat->GetLanguage()); + if ( bUseStarFormat ) + { + pFormat->SetStarFormatSupport( true ); + } + pFormat->GetOutputString(sString, sOutString, ppColor); + if ( bUseStarFormat ) + { + pFormat->SetStarFormatSupport( false ); + } + } +} + +void SvNumberFormatter::GetOutputString(const double& fOutNumber, + sal_uInt32 nFIndex, + OUString& sOutString, + const Color** ppColor, + bool bUseStarFormat ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if (bNoZero && fOutNumber == 0.0) + { + sOutString.clear(); + return; + } + SvNumberformat* pFormat = ImpSubstituteEntry( GetFormatEntry( nFIndex )); + if (!pFormat) + pFormat = GetFormatEntry(ZF_STANDARD); + ChangeIntl(pFormat->GetLanguage()); + if ( bUseStarFormat ) + pFormat->SetStarFormatSupport( true ); + pFormat->GetOutputString(fOutNumber, sOutString, ppColor); + if ( bUseStarFormat ) + pFormat->SetStarFormatSupport( false ); +} + +bool SvNumberFormatter::GetPreviewString(const OUString& sFormatString, + double fPreviewNumber, + OUString& sOutString, + const Color** ppColor, + LanguageType eLnge, + bool bUseStarFormat ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if (sFormatString.isEmpty()) // no empty string + { + return false; + } + if (eLnge == LANGUAGE_DONTKNOW) + { + eLnge = IniLnge; + } + ChangeIntl(eLnge); // change locale if necessary + eLnge = ActLnge; + sal_Int32 nCheckPos = -1; + OUString sTmpString = sFormatString; + SvNumberformat aEntry(sTmpString, + pFormatScanner.get(), + pStringScanner.get(), + nCheckPos, + eLnge); + if (nCheckPos == 0) // String ok + { + sal_uInt32 CLOffset = ImpGenerateCL(eLnge); // create new standard formats if necessary + sal_uInt32 nKey = ImpIsEntry(aEntry.GetFormatstring(),CLOffset, eLnge); + if (nKey != NUMBERFORMAT_ENTRY_NOT_FOUND) // already present + { + GetOutputString(fPreviewNumber, nKey, sOutString, ppColor, bUseStarFormat); + } + else + { + if ( bUseStarFormat ) + { + aEntry.SetStarFormatSupport( true ); + } + aEntry.GetOutputString(fPreviewNumber, sOutString, ppColor); + if ( bUseStarFormat ) + { + aEntry.SetStarFormatSupport( false ); + } + } + return true; + } + else + { + return false; + } +} + +bool SvNumberFormatter::GetPreviewStringGuess( const OUString& sFormatString, + double fPreviewNumber, + OUString& sOutString, + const Color** ppColor, + LanguageType eLnge ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if (sFormatString.isEmpty()) // no empty string + { + return false; + } + if (eLnge == LANGUAGE_DONTKNOW) + { + eLnge = IniLnge; + } + ChangeIntl( eLnge ); + eLnge = ActLnge; + bool bEnglish = (eLnge == LANGUAGE_ENGLISH_US); + + OUString aFormatStringUpper( xCharClass->uppercase( sFormatString ) ); + sal_uInt32 nCLOffset = ImpGenerateCL( eLnge ); + sal_uInt32 nKey = ImpIsEntry( aFormatStringUpper, nCLOffset, eLnge ); + if ( nKey != NUMBERFORMAT_ENTRY_NOT_FOUND ) + { + // Target format present + GetOutputString( fPreviewNumber, nKey, sOutString, ppColor ); + return true; + } + + std::optional pEntry; + sal_Int32 nCheckPos = -1; + OUString sTmpString; + + if ( bEnglish ) + { + sTmpString = sFormatString; + pEntry.emplace( sTmpString, pFormatScanner.get(), + pStringScanner.get(), nCheckPos, eLnge ); + } + else + { + nCLOffset = ImpGenerateCL( LANGUAGE_ENGLISH_US ); + nKey = ImpIsEntry( aFormatStringUpper, nCLOffset, LANGUAGE_ENGLISH_US ); + bool bEnglishFormat = (nKey != NUMBERFORMAT_ENTRY_NOT_FOUND); + + // Try English -> other or convert english to other + LanguageType eFormatLang = LANGUAGE_ENGLISH_US; + pFormatScanner->SetConvertMode( LANGUAGE_ENGLISH_US, eLnge, false, false); + sTmpString = sFormatString; + pEntry.emplace( sTmpString, pFormatScanner.get(), + pStringScanner.get(), nCheckPos, eFormatLang ); + pFormatScanner->SetConvertMode( false ); + ChangeIntl( eLnge ); + + if ( !bEnglishFormat ) + { + if ( nCheckPos != 0 || xTransliteration->isEqual( sFormatString, + pEntry->GetFormatstring() ) ) + { + // other Format + // Force locale's keywords. + pFormatScanner->ChangeIntl( ImpSvNumberformatScan::KeywordLocalization::LocaleLegacy ); + sTmpString = sFormatString; + pEntry.emplace( sTmpString, pFormatScanner.get(), + pStringScanner.get(), nCheckPos, eLnge ); + } + else + { + // verify english + sal_Int32 nCheckPos2 = -1; + // try other --> english + eFormatLang = eLnge; + pFormatScanner->SetConvertMode( eLnge, LANGUAGE_ENGLISH_US, false, false); + sTmpString = sFormatString; + SvNumberformat aEntry2( sTmpString, pFormatScanner.get(), + pStringScanner.get(), nCheckPos2, eFormatLang ); + pFormatScanner->SetConvertMode( false ); + ChangeIntl( eLnge ); + if ( nCheckPos2 == 0 && !xTransliteration->isEqual( sFormatString, + aEntry2.GetFormatstring() ) ) + { + // other Format + // Force locale's keywords. + pFormatScanner->ChangeIntl( ImpSvNumberformatScan::KeywordLocalization::LocaleLegacy ); + sTmpString = sFormatString; + pEntry.emplace( sTmpString, pFormatScanner.get(), + pStringScanner.get(), nCheckPos, eLnge ); + } + } + } + } + + if (nCheckPos == 0) // String ok + { + ImpGenerateCL( eLnge ); // create new standard formats if necessary + pEntry->GetOutputString( fPreviewNumber, sOutString, ppColor ); + return true; + } + return false; +} + +bool SvNumberFormatter::GetPreviewString( const OUString& sFormatString, + const OUString& sPreviewString, + OUString& sOutString, + const Color** ppColor, + LanguageType eLnge ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if (sFormatString.isEmpty()) // no empty string + { + return false; + } + if (eLnge == LANGUAGE_DONTKNOW) + { + eLnge = IniLnge; + } + ChangeIntl(eLnge); // switch if needed + eLnge = ActLnge; + sal_Int32 nCheckPos = -1; + OUString sTmpString = sFormatString; + SvNumberformat aEntry( sTmpString, + pFormatScanner.get(), + pStringScanner.get(), + nCheckPos, + eLnge); + if (nCheckPos == 0) // String ok + { + // May have to create standard formats for this locale. + sal_uInt32 CLOffset = ImpGenerateCL(eLnge); + sal_uInt32 nKey = ImpIsEntry( aEntry.GetFormatstring(), CLOffset, eLnge); + if (nKey != NUMBERFORMAT_ENTRY_NOT_FOUND) // already present + { + GetOutputString( sPreviewString, nKey, sOutString, ppColor); + } + else + { + // If the format is valid but not a text format and does not + // include a text subformat, an empty string would result. Same as + // in SvNumberFormatter::GetOutputString() + if (aEntry.IsTextFormat() || aEntry.HasTextFormat()) + { + aEntry.GetOutputString( sPreviewString, sOutString, ppColor); + } + else + { + *ppColor = nullptr; + sOutString = sPreviewString; + } + } + return true; + } + else + { + return false; + } +} + +sal_uInt32 SvNumberFormatter::TestNewString(const OUString& sFormatString, + LanguageType eLnge) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if (sFormatString.isEmpty()) // no empty string + { + return NUMBERFORMAT_ENTRY_NOT_FOUND; + } + if (eLnge == LANGUAGE_DONTKNOW) + { + eLnge = IniLnge; + } + ChangeIntl(eLnge); // change locale if necessary + eLnge = ActLnge; + sal_uInt32 nRes; + sal_Int32 nCheckPos = -1; + OUString sTmpString = sFormatString; + SvNumberformat aEntry(sTmpString, + pFormatScanner.get(), + pStringScanner.get(), + nCheckPos, + eLnge); + if (nCheckPos == 0) // String ok + { + sal_uInt32 CLOffset = ImpGenerateCL(eLnge); // create new standard formats if necessary + nRes = ImpIsEntry(aEntry.GetFormatstring(),CLOffset, eLnge); + // already present? + } + else + { + nRes = NUMBERFORMAT_ENTRY_NOT_FOUND; + } + return nRes; +} + +SvNumberformat* SvNumberFormatter::ImpInsertFormat( const css::i18n::NumberFormatCode& rCode, + sal_uInt32 nPos, bool bAfterChangingSystemCL, + sal_Int16 nOrgIndex ) +{ + SAL_WARN_IF( NF_INDEX_TABLE_RESERVED_START <= rCode.Index && rCode.Index < NF_INDEX_TABLE_ENTRIES, + "svl.numbers", "i18npool locale '" << maLanguageTag.getBcp47() << + "' uses reserved formatIndex value " << rCode.Index << ", next free: " << NF_INDEX_TABLE_ENTRIES << + " Please see description in include/svl/zforlist.hxx at end of enum NfIndexTableOffset"); + assert( (rCode.Index < NF_INDEX_TABLE_RESERVED_START || NF_INDEX_TABLE_ENTRIES <= rCode.Index) && + "reserved formatIndex, see warning above"); + + OUString aCodeStr( rCode.Code ); + if ( rCode.Index < NF_INDEX_TABLE_RESERVED_START && + rCode.Usage == css::i18n::KNumberFormatUsage::CURRENCY && + rCode.Index != NF_CURRENCY_1000DEC2_CCC ) + { // strip surrounding [$...] on automatic currency + if ( aCodeStr.indexOf( "[$" ) >= 0) + aCodeStr = SvNumberformat::StripNewCurrencyDelimiters( aCodeStr ); + else + { + if (LocaleDataWrapper::areChecksEnabled() && + rCode.Index != NF_CURRENCY_1000DEC2_CCC ) + { + OUString aMsg = "SvNumberFormatter::ImpInsertFormat: no [$...] on currency format code, index " + + OUString::number( rCode.Index) + + ":\n" + + rCode.Code; + LocaleDataWrapper::outputCheckMessage( xLocaleData->appendLocaleInfo( aMsg)); + } + } + } + sal_Int32 nCheckPos = 0; + std::unique_ptr pFormat(new SvNumberformat(aCodeStr, + pFormatScanner.get(), + pStringScanner.get(), + nCheckPos, + ActLnge)); + if (nCheckPos != 0) + { + if (LocaleDataWrapper::areChecksEnabled()) + { + OUString aMsg = "SvNumberFormatter::ImpInsertFormat: bad format code, index " + + OUString::number( rCode.Index ) + + "\n" + + rCode.Code; + LocaleDataWrapper::outputCheckMessage( xLocaleData->appendLocaleInfo( aMsg)); + } + return nullptr; + } + if ( rCode.Index >= NF_INDEX_TABLE_RESERVED_START ) + { + sal_uInt32 nCLOffset = nPos - (nPos % SV_COUNTRY_LANGUAGE_OFFSET); + sal_uInt32 nKey = ImpIsEntry( aCodeStr, nCLOffset, ActLnge ); + if ( nKey != NUMBERFORMAT_ENTRY_NOT_FOUND ) + { + // If bAfterChangingSystemCL there will definitely be some dups, + // don't cry then. + if (LocaleDataWrapper::areChecksEnabled() && !bAfterChangingSystemCL) + { + // Test for duplicate indexes in locale data. + switch ( nOrgIndex ) + { + // These may be dups of integer versions for locales where + // currencies have no decimals like Italian Lira. + case NF_CURRENCY_1000DEC2 : // NF_CURRENCY_1000INT + case NF_CURRENCY_1000DEC2_RED : // NF_CURRENCY_1000INT_RED + case NF_CURRENCY_1000DEC2_DASHED : // NF_CURRENCY_1000INT_RED + break; + default: + { + OUString aMsg = "SvNumberFormatter::ImpInsertFormat: dup format code, index " + + OUString::number( rCode.Index ) + + "\n" + + rCode.Code; + LocaleDataWrapper::outputCheckMessage( xLocaleData->appendLocaleInfo( aMsg)); + } + } + } + return nullptr; + } + else if ( nPos - nCLOffset >= SV_COUNTRY_LANGUAGE_OFFSET ) + { + if (LocaleDataWrapper::areChecksEnabled()) + { + OUString aMsg = "SvNumberFormatter::ImpInsertFormat: too many format codes, index " + + OUString::number( rCode.Index ) + + "\n" + + rCode.Code; + LocaleDataWrapper::outputCheckMessage( xLocaleData->appendLocaleInfo( aMsg)); + } + return nullptr; + } + } + auto pFormat2 = pFormat.get(); + if ( !aFTable.emplace( nPos, std::move(pFormat) ).second ) + { + if (LocaleDataWrapper::areChecksEnabled()) + { + OUString aMsg = "ImpInsertFormat: can't insert number format key pos: " + + OUString::number( nPos ) + + ", code index " + + OUString::number( rCode.Index ) + + "\n" + + rCode.Code; + LocaleDataWrapper::outputCheckMessage( xLocaleData->appendLocaleInfo( aMsg)); + } + else + { + SAL_WARN( "svl.numbers", "SvNumberFormatter::ImpInsertFormat: dup position"); + } + return nullptr; + } + if ( rCode.Default ) + pFormat2->SetStandard(); + if ( !rCode.DefaultName.isEmpty() ) + pFormat2->SetComment( rCode.DefaultName ); + return pFormat2; +} + +void SvNumberFormatter::GetFormatSpecialInfo(sal_uInt32 nFormat, + bool& bThousand, + bool& IsRed, + sal_uInt16& nPrecision, + sal_uInt16& nLeadingCnt) + +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + SvNumberformat* pFormat = GetFormatEntry( nFormat ); + if (pFormat) + pFormat->GetFormatSpecialInfo(bThousand, IsRed, + nPrecision, nLeadingCnt); + else + { + bThousand = false; + IsRed = false; + nPrecision = pFormatScanner->GetStandardPrec(); + nLeadingCnt = 0; + } +} + +sal_uInt16 SvNumberFormatter::GetFormatPrecision( sal_uInt32 nFormat ) const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + const SvNumberformat* pFormat = GetFormatEntry( nFormat ); + if ( pFormat ) + return pFormat->GetFormatPrecision(); + else + return pFormatScanner->GetStandardPrec(); +} + +sal_uInt16 SvNumberFormatter::GetFormatIntegerDigits( sal_uInt32 nFormat ) const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + const SvNumberformat* pFormat = GetFormatEntry( nFormat ); + if ( pFormat ) + return pFormat->GetFormatIntegerDigits(); + else + return 1; +} + +OUString SvNumberFormatter::GetFormatDecimalSep( sal_uInt32 nFormat ) const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + const SvNumberformat* pFormat = GetFormatEntry(nFormat); + if (!pFormat) + { + return GetNumDecimalSep(); + } + return GetLangDecimalSep( pFormat->GetLanguage()); +} + +OUString SvNumberFormatter::GetLangDecimalSep( LanguageType nLang ) const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if (nLang == ActLnge) + { + return GetNumDecimalSep(); + } + OUString aRet; + LanguageType eSaveLang = xLocaleData.getCurrentLanguage(); + if (nLang == eSaveLang) + { + aRet = xLocaleData->getNumDecimalSep(); + } + else + { + LanguageTag aSaveLocale( xLocaleData->getLanguageTag() ); + const_cast(this)->xLocaleData.changeLocale( LanguageTag( nLang)); + aRet = xLocaleData->getNumDecimalSep(); + const_cast(this)->xLocaleData.changeLocale( aSaveLocale ); + } + return aRet; +} + + +sal_uInt32 SvNumberFormatter::GetFormatSpecialInfo( const OUString& rFormatString, + bool& bThousand, bool& IsRed, sal_uInt16& nPrecision, + sal_uInt16& nLeadingCnt, LanguageType eLnge ) + +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if (eLnge == LANGUAGE_DONTKNOW) + { + eLnge = IniLnge; + } + ChangeIntl(eLnge); // change locale if necessary + eLnge = ActLnge; + OUString aTmpStr( rFormatString ); + sal_Int32 nCheckPos = 0; + SvNumberformat aFormat( aTmpStr, pFormatScanner.get(), + pStringScanner.get(), nCheckPos, eLnge ); + if ( nCheckPos == 0 ) + { + aFormat.GetFormatSpecialInfo( bThousand, IsRed, nPrecision, nLeadingCnt ); + } + else + { + bThousand = false; + IsRed = false; + nPrecision = pFormatScanner->GetStandardPrec(); + nLeadingCnt = 0; + } + return nCheckPos; +} + +OUString SvNumberFormatter::GetCalcCellReturn( sal_uInt32 nFormat ) const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + const SvNumberformat* pFormat = GetFormatEntry( nFormat ); + if (!pFormat) + return "G"; + + OUString aStr; + bool bAppendPrec = true; + sal_uInt16 nPrec, nLeading; + bool bThousand, bIsRed; + pFormat->GetFormatSpecialInfo( bThousand, bIsRed, nPrec, nLeading ); + + switch (pFormat->GetMaskedType()) + { + case SvNumFormatType::NUMBER: + if (bThousand) + aStr = ","; + else + aStr = "F"; + break; + case SvNumFormatType::CURRENCY: + aStr = "C"; + break; + case SvNumFormatType::SCIENTIFIC: + aStr = "S"; + break; + case SvNumFormatType::PERCENT: + aStr = "P"; + break; + default: + { + bAppendPrec = false; + switch (GetIndexTableOffset( nFormat )) + { + case NF_DATE_SYSTEM_SHORT: + case NF_DATE_SYS_DMMMYY: + case NF_DATE_SYS_DDMMYY: + case NF_DATE_SYS_DDMMYYYY: + case NF_DATE_SYS_DMMMYYYY: + case NF_DATE_DIN_DMMMYYYY: + case NF_DATE_SYS_DMMMMYYYY: + case NF_DATE_DIN_DMMMMYYYY: aStr = "D1"; break; + case NF_DATE_SYS_DDMMM: aStr = "D2"; break; + case NF_DATE_SYS_MMYY: aStr = "D3"; break; + case NF_DATETIME_SYSTEM_SHORT_HHMM: + case NF_DATETIME_SYS_DDMMYYYY_HHMM: + case NF_DATETIME_SYS_DDMMYYYY_HHMMSS: + aStr = "D4"; break; + case NF_DATE_DIN_MMDD: aStr = "D5"; break; + case NF_TIME_HHMMSSAMPM: aStr = "D6"; break; + case NF_TIME_HHMMAMPM: aStr = "D7"; break; + case NF_TIME_HHMMSS: aStr = "D8"; break; + case NF_TIME_HHMM: aStr = "D9"; break; + default: aStr = "G"; + } + } + } + + if (bAppendPrec) + aStr += OUString::number(nPrec); + + if (pFormat->GetColor( 1 )) + aStr += "-"; // negative color + + /* FIXME: this probably should not match on literal strings and only be + * performed on number or currency formats, but it is what Calc originally + * implemented. */ + if (pFormat->GetFormatstring().indexOf('(') != -1) + aStr += "()"; + + return aStr; +} + +sal_Int32 SvNumberFormatter::ImpGetFormatCodeIndex( + css::uno::Sequence< css::i18n::NumberFormatCode >& rSeq, + const NfIndexTableOffset nTabOff ) +{ + auto pSeq = std::find_if(std::cbegin(rSeq), std::cend(rSeq), + [nTabOff](const css::i18n::NumberFormatCode& rCode) { return rCode.Index == nTabOff; }); + if (pSeq != std::cend(rSeq)) + return static_cast(std::distance(std::cbegin(rSeq), pSeq)); + if (LocaleDataWrapper::areChecksEnabled() && (nTabOff < NF_CURRENCY_START + || NF_CURRENCY_END < nTabOff || nTabOff == NF_CURRENCY_1000INT + || nTabOff == NF_CURRENCY_1000INT_RED + || nTabOff == NF_CURRENCY_1000DEC2_CCC)) + { // currency entries with decimals might not exist, e.g. Italian Lira + OUString aMsg = "SvNumberFormatter::ImpGetFormatCodeIndex: not found: " + + OUString::number( nTabOff ); + LocaleDataWrapper::outputCheckMessage( xLocaleData->appendLocaleInfo(aMsg)); + } + if ( rSeq.hasElements() ) + { + // look for a preset default + pSeq = std::find_if(std::cbegin(rSeq), std::cend(rSeq), + [](const css::i18n::NumberFormatCode& rCode) { return rCode.Default; }); + if (pSeq != std::cend(rSeq)) + return static_cast(std::distance(std::cbegin(rSeq), pSeq)); + // currencies are special, not all format codes must exist, but all + // builtin number format key index positions must have a format assigned + if ( NF_CURRENCY_START <= nTabOff && nTabOff <= NF_CURRENCY_END ) + { + // look for a format with decimals + pSeq = std::find_if(std::cbegin(rSeq), std::cend(rSeq), + [](const css::i18n::NumberFormatCode& rCode) { return rCode.Index == NF_CURRENCY_1000DEC2; }); + if (pSeq != std::cend(rSeq)) + return static_cast(std::distance(std::cbegin(rSeq), pSeq)); + // last resort: look for a format without decimals + pSeq = std::find_if(std::cbegin(rSeq), std::cend(rSeq), + [](const css::i18n::NumberFormatCode& rCode) { return rCode.Index == NF_CURRENCY_1000INT; }); + if (pSeq != std::cend(rSeq)) + return static_cast(std::distance(std::cbegin(rSeq), pSeq)); + } + } + else + { // we need at least _some_ format + rSeq = { css::i18n::NumberFormatCode() }; + rSeq.getArray()[0].Code = "0" + GetNumDecimalSep() + "############"; + } + return 0; +} + + +void SvNumberFormatter::ImpAdjustFormatCodeDefault( + css::i18n::NumberFormatCode * pFormatArr, + sal_Int32 nCnt ) +{ + if ( !nCnt ) + return; + if (LocaleDataWrapper::areChecksEnabled()) + { + // check the locale data for correctness + OStringBuffer aMsg; + sal_Int32 nElem, nShort, nMedium, nLong, nShortDef, nMediumDef, nLongDef; + nShort = nMedium = nLong = nShortDef = nMediumDef = nLongDef = -1; + for ( nElem = 0; nElem < nCnt; nElem++ ) + { + switch ( pFormatArr[nElem].Type ) + { + case i18n::KNumberFormatType::SHORT : + nShort = nElem; + break; + case i18n::KNumberFormatType::MEDIUM : + nMedium = nElem; + break; + case i18n::KNumberFormatType::LONG : + nLong = nElem; + break; + default: + aMsg.append("unknown type"); + } + if ( pFormatArr[nElem].Default ) + { + switch ( pFormatArr[nElem].Type ) + { + case i18n::KNumberFormatType::SHORT : + if ( nShortDef != -1 ) + aMsg.append("dupe short type default"); + nShortDef = nElem; + break; + case i18n::KNumberFormatType::MEDIUM : + if ( nMediumDef != -1 ) + aMsg.append("dupe medium type default"); + nMediumDef = nElem; + break; + case i18n::KNumberFormatType::LONG : + if ( nLongDef != -1 ) + aMsg.append("dupe long type default"); + nLongDef = nElem; + break; + } + } + if (!aMsg.isEmpty()) + { + aMsg.insert(0, "SvNumberFormatter::ImpAdjustFormatCodeDefault: "); + aMsg.append("\nXML locale data FormatElement formatindex: "); + aMsg.append(static_cast(pFormatArr[nElem].Index)); + OUString aUMsg(OStringToOUString(aMsg.makeStringAndClear(), + RTL_TEXTENCODING_ASCII_US)); + LocaleDataWrapper::outputCheckMessage(xLocaleData->appendLocaleInfo(aUMsg)); + } + } + if ( nShort != -1 && nShortDef == -1 ) + aMsg.append("no short type default "); + if ( nMedium != -1 && nMediumDef == -1 ) + aMsg.append("no medium type default "); + if ( nLong != -1 && nLongDef == -1 ) + aMsg.append("no long type default "); + if (!aMsg.isEmpty()) + { + aMsg.insert(0, "SvNumberFormatter::ImpAdjustFormatCodeDefault: "); + aMsg.append("\nXML locale data FormatElement group of: "); + OUString aUMsg(OStringToOUString(aMsg.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US)); + LocaleDataWrapper::outputCheckMessage( + xLocaleData->appendLocaleInfo(OUStringConcatenation(aUMsg + pFormatArr[0].NameID))); + } + } + // find the default (medium preferred, then long) and reset all other defaults + sal_Int32 nElem, nDef, nMedium; + nDef = nMedium = -1; + for ( nElem = 0; nElem < nCnt; nElem++ ) + { + if ( pFormatArr[nElem].Default ) + { + switch ( pFormatArr[nElem].Type ) + { + case i18n::KNumberFormatType::MEDIUM : + nDef = nMedium = nElem; + break; + case i18n::KNumberFormatType::LONG : + if ( nMedium == -1 ) + nDef = nElem; + [[fallthrough]]; + default: + if ( nDef == -1 ) + nDef = nElem; + pFormatArr[nElem].Default = false; + } + } + } + if ( nDef == -1 ) + nDef = 0; + pFormatArr[nDef].Default = true; +} + +SvNumberformat* SvNumberFormatter::GetFormatEntry( sal_uInt32 nKey ) +{ + auto it = aFTable.find( nKey); + if (it != aFTable.end()) + return it->second.get(); + return nullptr; +} + +const SvNumberformat* SvNumberFormatter::GetFormatEntry( sal_uInt32 nKey ) const +{ + return GetEntry( nKey); +} + +const SvNumberformat* SvNumberFormatter::GetEntry( sal_uInt32 nKey ) const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + auto it = aFTable.find( nKey); + if (it != aFTable.end()) + return it->second.get(); + return nullptr; +} + +const SvNumberformat* SvNumberFormatter::GetSubstitutedEntry( sal_uInt32 nKey, sal_uInt32 & o_rNewKey ) const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + // A tad ugly, but GetStandardFormat() and GetFormatIndex() in + // ImpSubstituteEntry() may have to add the LANGUAGE_SYSTEM formats if not + // already present (which in practice most times they are). + SvNumberFormatter* pThis = const_cast(this); + return pThis->ImpSubstituteEntry( pThis->GetFormatEntry( nKey), &o_rNewKey); +} + +SvNumberformat* SvNumberFormatter::ImpSubstituteEntry( SvNumberformat* pFormat, sal_uInt32 * o_pRealKey ) +{ + if (!pFormat || !pFormat->IsSubstituted()) + return pFormat; + + // XXX NOTE: substitution can not be done in GetFormatEntry() as otherwise + // to be substituted formats would "vanish", i.e. from the number formatter + // dialog or when exporting to Excel. + + sal_uInt32 nKey; + if (pFormat->IsSystemTimeFormat()) + /* TODO: should we have NF_TIME_SYSTEM for consistency? */ + nKey = GetStandardFormat( SvNumFormatType::TIME, LANGUAGE_SYSTEM); + else if (pFormat->IsSystemLongDateFormat()) + /* TODO: either that above, or have a long option for GetStandardFormat() */ + nKey = GetFormatIndex( NF_DATE_SYSTEM_LONG, LANGUAGE_SYSTEM); + else + return pFormat; + + if (o_pRealKey) + *o_pRealKey = nKey; + auto it = aFTable.find( nKey); + return it == aFTable.end() ? nullptr : it->second.get(); +} + +void SvNumberFormatter::ImpGenerateFormats( sal_uInt32 CLOffset, bool bNoAdditionalFormats ) +{ + bool bOldConvertMode = pFormatScanner->GetConvertMode(); + if (bOldConvertMode) + { + pFormatScanner->SetConvertMode(false); // switch off for this function + } + + css::lang::Locale aLocale = GetLanguageTag().getLocale(); + css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext ); + sal_Int32 nIdx; + + // Number + uno::Sequence< i18n::NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( i18n::KNumberFormatUsage::FIXED_NUMBER, aLocale ); + ImpAdjustFormatCodeDefault( aFormatSeq.getArray(), aFormatSeq.getLength() ); + + // General + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_NUMBER_STANDARD ); + SvNumberformat* pStdFormat = ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD /* NF_NUMBER_STANDARD */ ); + if (pStdFormat) + { + // This is _the_ standard format. + if (LocaleDataWrapper::areChecksEnabled() && pStdFormat->GetType() != SvNumFormatType::NUMBER) + { + LocaleDataWrapper::outputCheckMessage( xLocaleData-> + appendLocaleInfo( u"SvNumberFormatter::ImpGenerateFormats: General format not NUMBER")); + } + pStdFormat->SetType( SvNumFormatType::NUMBER ); + pStdFormat->SetStandard(); + pStdFormat->SetLastInsertKey( SV_MAX_COUNT_STANDARD_FORMATS, SvNumberformat::FormatterPrivateAccess() ); + } + else + { + if (LocaleDataWrapper::areChecksEnabled()) + { + LocaleDataWrapper::outputCheckMessage( xLocaleData-> + appendLocaleInfo( u"SvNumberFormatter::ImpGenerateFormats: General format not insertable, nothing will work")); + } + } + + { + // Boolean + OUString aFormatCode = pFormatScanner->GetBooleanString(); + sal_Int32 nCheckPos = 0; + + std::unique_ptr pNewFormat(new SvNumberformat( aFormatCode, pFormatScanner.get(), + pStringScanner.get(), nCheckPos, ActLnge )); + pNewFormat->SetType(SvNumFormatType::LOGICAL); + pNewFormat->SetStandard(); + if ( !aFTable.emplace(CLOffset + ZF_STANDARD_LOGICAL /* NF_BOOLEAN */, + std::move(pNewFormat)).second ) + { + SAL_WARN( "svl.numbers", "SvNumberFormatter::ImpGenerateFormats: dup position Boolean"); + } + + // Text + aFormatCode = "@"; + pNewFormat.reset(new SvNumberformat( aFormatCode, pFormatScanner.get(), + pStringScanner.get(), nCheckPos, ActLnge )); + pNewFormat->SetType(SvNumFormatType::TEXT); + pNewFormat->SetStandard(); + if ( !aFTable.emplace( CLOffset + ZF_STANDARD_TEXT /* NF_TEXT */, + std::move(pNewFormat)).second ) + { + SAL_WARN( "svl.numbers", "SvNumberFormatter::ImpGenerateFormats: dup position Text"); + } + } + + // 0 + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_NUMBER_INT ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD+1 /* NF_NUMBER_INT */ ); + + // 0.00 + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_NUMBER_DEC2 ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD+2 /* NF_NUMBER_DEC2 */ ); + + // #,##0 + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_NUMBER_1000INT ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD+3 /* NF_NUMBER_1000INT */ ); + + // #,##0.00 + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_NUMBER_1000DEC2 ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD+4 /* NF_NUMBER_1000DEC2 */ ); + + // #.##0,00 System country/language dependent + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_NUMBER_SYSTEM ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD+5 /* NF_NUMBER_SYSTEM */ ); + + + // Percent number + aFormatSeq = xNFC->getAllFormatCode( i18n::KNumberFormatUsage::PERCENT_NUMBER, aLocale ); + ImpAdjustFormatCodeDefault( aFormatSeq.getArray(), aFormatSeq.getLength() ); + + // 0% + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_PERCENT_INT ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_PERCENT /* NF_PERCENT_INT */ ); + + // 0.00% + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_PERCENT_DEC2 ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_PERCENT+1 /* NF_PERCENT_DEC2 */ ); + + + // Currency. NO default standard option! Default is determined of locale + // data default currency and format is generated if needed. + aFormatSeq = xNFC->getAllFormatCode( i18n::KNumberFormatUsage::CURRENCY, aLocale ); + if (LocaleDataWrapper::areChecksEnabled()) + { + // though no default desired here, test for correctness of locale data + ImpAdjustFormatCodeDefault( aFormatSeq.getArray(), aFormatSeq.getLength() ); + } + + // #,##0 + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_CURRENCY_1000INT ); + // Just copy the format, to avoid COW on sequence after each possible reallocation + auto aFormat = aFormatSeq[nIdx]; + aFormat.Default = false; + ImpInsertFormat( aFormat, + CLOffset + ZF_STANDARD_CURRENCY /* NF_CURRENCY_1000INT */ ); + + // #,##0.00 + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_CURRENCY_1000DEC2 ); + aFormat = aFormatSeq[nIdx]; + aFormat.Default = false; + ImpInsertFormat( aFormat, + CLOffset + ZF_STANDARD_CURRENCY+1 /* NF_CURRENCY_1000DEC2 */ ); + + // #,##0 negative red + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_CURRENCY_1000INT_RED ); + aFormat = aFormatSeq[nIdx]; + aFormat.Default = false; + ImpInsertFormat(aFormat, + CLOffset + ZF_STANDARD_CURRENCY+2 /* NF_CURRENCY_1000INT_RED */ ); + + // #,##0.00 negative red + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_CURRENCY_1000DEC2_RED ); + aFormat = aFormatSeq[nIdx]; + aFormat.Default = false; + ImpInsertFormat(aFormat, + CLOffset + ZF_STANDARD_CURRENCY+3 /* NF_CURRENCY_1000DEC2_RED */ ); + + // #,##0.00 USD + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_CURRENCY_1000DEC2_CCC ); + aFormat = aFormatSeq[nIdx]; + aFormat.Default = false; + ImpInsertFormat( aFormat, + CLOffset + ZF_STANDARD_CURRENCY+4 /* NF_CURRENCY_1000DEC2_CCC */ ); + + // #.##0,-- + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_CURRENCY_1000DEC2_DASHED ); + aFormat = aFormatSeq[nIdx]; + aFormat.Default = false; + ImpInsertFormat(aFormat, + CLOffset + ZF_STANDARD_CURRENCY+5 /* NF_CURRENCY_1000DEC2_DASHED */ ); + + + // Date + aFormatSeq = xNFC->getAllFormatCode( i18n::KNumberFormatUsage::DATE, aLocale ); + ImpAdjustFormatCodeDefault( aFormatSeq.getArray(), aFormatSeq.getLength() ); + + // DD.MM.YY System + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYSTEM_SHORT ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE /* NF_DATE_SYSTEM_SHORT */ ); + + // NN DD.MMM YY + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_DEF_NNDDMMMYY ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE+1 /* NF_DATE_DEF_NNDDMMMYY */ ); + + // DD.MM.YY def/System + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_MMYY ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE+2 /* NF_DATE_SYS_MMYY */ ); + + // DD MMM + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_DDMMM ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE+3 /* NF_DATE_SYS_DDMMM */ ); + + // MMMM + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_MMMM ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE+4 /* NF_DATE_MMMM */ ); + + // QQ YY + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_QQJJ ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE+5 /* NF_DATE_QQJJ */ ); + + // DD.MM.YYYY was DD.MM.[YY]YY + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_DDMMYYYY ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE+6 /* NF_DATE_SYS_DDMMYYYY */ ); + + // DD.MM.YY def/System + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_DDMMYY ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE+7 /* NF_DATE_SYS_DDMMYY */ ); + + // NNN, D. MMMM YYYY System + // Long day of week: "NNNN" instead of "NNN," because of compatibility + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYSTEM_LONG ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE+8 /* NF_DATE_SYSTEM_LONG */ ); + + // Hard coded but system (regional settings) delimiters dependent long date formats + + // D. MMM YY def/System + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_DMMMYY ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE+9 /* NF_DATE_SYS_DMMMYY */ ); + + // D. MMM YYYY def/System + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_DMMMYYYY ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE_SYS_DMMMYYYY /* NF_DATE_SYS_DMMMYYYY */ ); + + // D. MMMM YYYY def/System + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_DMMMMYYYY ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE_SYS_DMMMMYYYY /* NF_DATE_SYS_DMMMMYYYY */ ); + + // NN, D. MMM YY def/System + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_NNDMMMYY ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE_SYS_NNDMMMYY /* NF_DATE_SYS_NNDMMMYY */ ); + + // NN, D. MMMM YYYY def/System + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_NNDMMMMYYYY ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE_SYS_NNDMMMMYYYY /* NF_DATE_SYS_NNDMMMMYYYY */ ); + + // NNN, D. MMMM YYYY def/System + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_NNNNDMMMMYYYY ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE_SYS_NNNNDMMMMYYYY /* NF_DATE_SYS_NNNNDMMMMYYYY */ ); + + // Hard coded DIN (Deutsche Industrie Norm) and EN (European Norm) date formats + + // D. MMM. YYYY DIN/EN + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_DIN_DMMMYYYY ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE_DIN_DMMMYYYY /* NF_DATE_DIN_DMMMYYYY */ ); + + // D. MMMM YYYY DIN/EN + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_DIN_DMMMMYYYY ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE_DIN_DMMMMYYYY /* NF_DATE_DIN_DMMMMYYYY */ ); + + // MM-DD DIN/EN + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_DIN_MMDD ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE_DIN_MMDD /* NF_DATE_DIN_MMDD */ ); + + // YY-MM-DD DIN/EN + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_DIN_YYMMDD ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE_DIN_YYMMDD /* NF_DATE_DIN_YYMMDD */ ); + + // YYYY-MM-DD DIN/EN/ISO + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_DIN_YYYYMMDD ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATE_DIN_YYYYMMDD /* NF_DATE_DIN_YYYYMMDD */ ); + + + // Time + aFormatSeq = xNFC->getAllFormatCode( i18n::KNumberFormatUsage::TIME, aLocale ); + ImpAdjustFormatCodeDefault( aFormatSeq.getArray(), aFormatSeq.getLength() ); + + // HH:MM + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_TIME_HHMM ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_TIME /* NF_TIME_HHMM */ ); + + // HH:MM:SS + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_TIME_HHMMSS ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_TIME+1 /* NF_TIME_HHMMSS */ ); + + // HH:MM AM/PM + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_TIME_HHMMAMPM ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_TIME+2 /* NF_TIME_HHMMAMPM */ ); + + // HH:MM:SS AM/PM + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_TIME_HHMMSSAMPM ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_TIME+3 /* NF_TIME_HHMMSSAMPM */ ); + + // [HH]:MM:SS + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_TIME_HH_MMSS ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_TIME+4 /* NF_TIME_HH_MMSS */ ); + + // MM:SS,00 + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_TIME_MMSS00 ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_TIME+5 /* NF_TIME_MMSS00 */ ); + + // [HH]:MM:SS,00 + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_TIME_HH_MMSS00 ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_TIME+6 /* NF_TIME_HH_MMSS00 */ ); + + + // DateTime + aFormatSeq = xNFC->getAllFormatCode( i18n::KNumberFormatUsage::DATE_TIME, aLocale ); + ImpAdjustFormatCodeDefault( aFormatSeq.getArray(), aFormatSeq.getLength() ); + + // DD.MM.YY HH:MM System + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATETIME_SYSTEM_SHORT_HHMM ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATETIME /* NF_DATETIME_SYSTEM_SHORT_HHMM */ ); + + // DD.MM.YYYY HH:MM:SS System + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATETIME_SYS_DDMMYYYY_HHMMSS ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATETIME+1 /* NF_DATETIME_SYS_DDMMYYYY_HHMMSS */ ); + + // DD.MM.YYYY HH:MM System + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATETIME_SYS_DDMMYYYY_HHMM ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_DATETIME+2 /* NF_DATETIME_SYS_DDMMYYYY_HHMM */ ); + + const NfKeywordTable & rKeyword = pFormatScanner->GetKeywords(); + i18n::NumberFormatCode aSingleFormatCode; + aSingleFormatCode.Usage = i18n::KNumberFormatUsage::DATE_TIME; + + // YYYY-MM-DD HH:MM:SS ISO (with blank instead of 'T') + aSingleFormatCode.Code = + rKeyword[NF_KEY_YYYY] + "-" + + rKeyword[NF_KEY_MM] + "-" + + rKeyword[NF_KEY_DD] + " " + + rKeyword[NF_KEY_HH] + ":" + + rKeyword[NF_KEY_MMI] + ":" + + rKeyword[NF_KEY_SS]; + SvNumberformat* pFormat = ImpInsertFormat( aSingleFormatCode, + CLOffset + ZF_STANDARD_DATETIME+3 /* NF_DATETIME_ISO_YYYYMMDD_HHMMSS */ ); + assert(pFormat); + + // YYYY-MM-DD HH:MM:SS,000 ISO (with blank instead of 'T') and + // milliseconds and locale's time decimal separator + aSingleFormatCode.Code = + rKeyword[NF_KEY_YYYY] + "-" + + rKeyword[NF_KEY_MM] + "-" + + rKeyword[NF_KEY_DD] + " " + + rKeyword[NF_KEY_HH] + ":" + + rKeyword[NF_KEY_MMI] + ":" + + rKeyword[NF_KEY_SS] + GetLocaleData()->getTime100SecSep() + + "000"; + pFormat = ImpInsertFormat( aSingleFormatCode, + CLOffset + ZF_STANDARD_DATETIME+4 /* NF_DATETIME_ISO_YYYYMMDD_HHMMSS000 */ ); + assert(pFormat); + + // YYYY-MM-DD"T"HH:MM:SS ISO + aSingleFormatCode.Code = + rKeyword[NF_KEY_YYYY] + "-" + + rKeyword[NF_KEY_MM] + "-" + + rKeyword[NF_KEY_DD] + "\"T\"" + + rKeyword[NF_KEY_HH] + ":" + + rKeyword[NF_KEY_MMI] + ":" + + rKeyword[NF_KEY_SS]; + pFormat = ImpInsertFormat( aSingleFormatCode, + CLOffset + ZF_STANDARD_DATETIME+5 /* NF_DATETIME_ISO_YYYYMMDDTHHMMSS */ ); + assert(pFormat); + pFormat->SetComment("ISO 8601"); // not to be localized + + // YYYY-MM-DD"T"HH:MM:SS,000 ISO with milliseconds and ',' or '.' decimal separator + aSingleFormatCode.Code = + rKeyword[NF_KEY_YYYY] + "-" + + rKeyword[NF_KEY_MM] + "-" + + rKeyword[NF_KEY_DD] + "\"T\"" + + rKeyword[NF_KEY_HH] + ":" + + rKeyword[NF_KEY_MMI] + ":" + + rKeyword[NF_KEY_SS] + (GetLocaleData()->getTime100SecSep() == "." ? "." : ",") + + "000"; + pFormat = ImpInsertFormat( aSingleFormatCode, + CLOffset + ZF_STANDARD_DATETIME+6 /* NF_DATETIME_ISO_YYYYMMDDTHHMMSS000 */ ); + assert(pFormat); + pFormat->SetComment("ISO 8601"); // not to be localized + + + // Scientific number + aFormatSeq = xNFC->getAllFormatCode( i18n::KNumberFormatUsage::SCIENTIFIC_NUMBER, aLocale ); + ImpAdjustFormatCodeDefault( aFormatSeq.getArray(), aFormatSeq.getLength() ); + + // 0.00E+000 + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_SCIENTIFIC_000E000 ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_SCIENTIFIC /* NF_SCIENTIFIC_000E000 */ ); + + // 0.00E+00 + nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_SCIENTIFIC_000E00 ); + ImpInsertFormat( aFormatSeq[nIdx], + CLOffset + ZF_STANDARD_SCIENTIFIC+1 /* NF_SCIENTIFIC_000E00 */ ); + + + // Fraction number (no default option) + aSingleFormatCode.Usage = i18n::KNumberFormatUsage::FRACTION_NUMBER; + + // # ?/? + aSingleFormatCode.Code = "# ?/?"; + ImpInsertFormat( aSingleFormatCode, + CLOffset + ZF_STANDARD_FRACTION /* NF_FRACTION_1D */ ); + + // # ??/?? + //! "??/" would be interpreted by the compiler as a trigraph for '\' + aSingleFormatCode.Code = "# ?\?/?\?"; + ImpInsertFormat( aSingleFormatCode, + CLOffset + ZF_STANDARD_FRACTION+1 /* NF_FRACTION_2D */ ); + + // # ???/??? + //! "??/" would be interpreted by the compiler as a trigraph for '\' + aSingleFormatCode.Code = "# ?\?\?/?\?\?"; + ImpInsertFormat( aSingleFormatCode, + CLOffset + ZF_STANDARD_FRACTION+2 /* NF_FRACTION_3D */ ); + + // # ?/2 + aSingleFormatCode.Code = "# ?/2"; + ImpInsertFormat( aSingleFormatCode, + CLOffset + ZF_STANDARD_FRACTION+3 /* NF_FRACTION_2 */ ); + + // # ?/4 + aSingleFormatCode.Code = "# ?/4"; + ImpInsertFormat( aSingleFormatCode, + CLOffset + ZF_STANDARD_FRACTION+4 /* NF_FRACTION_4 */ ); + + // # ?/8 + aSingleFormatCode.Code = "# ?/8"; + ImpInsertFormat( aSingleFormatCode, + CLOffset + ZF_STANDARD_FRACTION+5 /* NF_FRACTION_8 */ ); + + // # ??/16 + aSingleFormatCode.Code = "# ?\?/16"; + ImpInsertFormat( aSingleFormatCode, + CLOffset + ZF_STANDARD_FRACTION+6 /* NF_FRACTION_16 */ ); + + // # ??/10 + aSingleFormatCode.Code = "# ?\?/10"; + ImpInsertFormat( aSingleFormatCode, + CLOffset + ZF_STANDARD_FRACTION+7 /* NF_FRACTION_10 */ ); + + // # ??/100 + aSingleFormatCode.Code = "# ?\?/100"; + ImpInsertFormat( aSingleFormatCode, + CLOffset + ZF_STANDARD_FRACTION+8 /* NF_FRACTION_100 */ ); + + + // Week of year + aSingleFormatCode.Code = rKeyword[NF_KEY_WW]; + ImpInsertFormat( aSingleFormatCode, + CLOffset + ZF_STANDARD_DATE_WW /* NF_DATE_WW */ ); + + // Now all additional format codes provided by I18N, but only if not + // changing SystemCL, then they are appended last after user defined. + if ( !bNoAdditionalFormats ) + { + ImpGenerateAdditionalFormats( CLOffset, xNFC, false ); + } + if (bOldConvertMode) + { + pFormatScanner->SetConvertMode(true); + } +} + + +void SvNumberFormatter::ImpGenerateAdditionalFormats( sal_uInt32 CLOffset, + css::uno::Reference< css::i18n::XNumberFormatCode > const & rNumberFormatCode, + bool bAfterChangingSystemCL ) +{ + SvNumberformat* pStdFormat = GetFormatEntry( CLOffset + ZF_STANDARD ); + if ( !pStdFormat ) + { + SAL_WARN( "svl.numbers", "ImpGenerateAdditionalFormats: no GENERAL format" ); + return ; + } + sal_uInt32 nPos = CLOffset + pStdFormat->GetLastInsertKey( SvNumberformat::FormatterPrivateAccess() ); + css::lang::Locale aLocale = GetLanguageTag().getLocale(); + + // All currencies, this time with [$...] which was stripped in + // ImpGenerateFormats for old "automatic" currency formats. + uno::Sequence< i18n::NumberFormatCode > aFormatSeq = rNumberFormatCode->getAllFormatCode( i18n::KNumberFormatUsage::CURRENCY, aLocale ); + sal_Int32 nCodes = aFormatSeq.getLength(); + auto aNonConstRange = asNonConstRange(aFormatSeq); + ImpAdjustFormatCodeDefault( aNonConstRange.begin(), nCodes); + for ( i18n::NumberFormatCode& rFormat : aNonConstRange ) + { + if ( nPos - CLOffset >= SV_COUNTRY_LANGUAGE_OFFSET ) + { + SAL_WARN( "svl.numbers", "ImpGenerateAdditionalFormats: too many formats" ); + break; // for + } + if ( rFormat.Index < NF_INDEX_TABLE_RESERVED_START && + rFormat.Index != NF_CURRENCY_1000DEC2_CCC ) + { // Insert only if not already inserted, but internal index must be + // above so ImpInsertFormat can distinguish it. + sal_Int16 nOrgIndex = rFormat.Index; + rFormat.Index = sal::static_int_cast< sal_Int16 >( + rFormat.Index + nCodes + NF_INDEX_TABLE_ENTRIES); + //! no default on currency + bool bDefault = rFormat.Default; + rFormat.Default = false; + if ( SvNumberformat* pNewFormat = ImpInsertFormat( rFormat, nPos+1, + bAfterChangingSystemCL, nOrgIndex ) ) + { + pNewFormat->SetAdditionalBuiltin(); + nPos++; + } + rFormat.Index = nOrgIndex; + rFormat.Default = bDefault; + } + } + + // All additional format codes provided by I18N that are not old standard + // index. Additional formats may define defaults, currently there is no + // check if more than one default of a usage/type combination is provided, + // like it is done for usage groups with ImpAdjustFormatCodeDefault(). + // There is no harm though, on first invocation ImpGetDefaultFormat() will + // use the first default encountered. + aFormatSeq = rNumberFormatCode->getAllFormatCodes( aLocale ); + for ( const auto& rFormat : std::as_const(aFormatSeq) ) + { + if ( nPos - CLOffset >= SV_COUNTRY_LANGUAGE_OFFSET ) + { + SAL_WARN( "svl.numbers", "ImpGenerateAdditionalFormats: too many formats" ); + break; // for + } + if ( rFormat.Index >= NF_INDEX_TABLE_RESERVED_START ) + { + if ( SvNumberformat* pNewFormat = ImpInsertFormat( rFormat, nPos+1, + bAfterChangingSystemCL ) ) + { + pNewFormat->SetAdditionalBuiltin(); + nPos++; + } + } + } + + pStdFormat->SetLastInsertKey( static_cast(nPos - CLOffset), SvNumberformat::FormatterPrivateAccess() ); +} + + +sal_Int32 SvNumberFormatter::ImpPosToken ( const OUStringBuffer & sFormat, sal_Unicode token, sal_Int32 nStartPos /* = 0*/ ) const +{ + sal_Int32 nLength = sFormat.getLength(); + for ( sal_Int32 i=nStartPos; i=0 ; i++ ) + { + switch(sFormat[i]) + { + case '\"' : // skip text + i = sFormat.indexOf('\"',i+1); + break; + case '[' : // skip condition + i = sFormat.indexOf(']',i+1); + break; + case '\\' : // skip escaped character + i++; + break; + case ';' : + if (token == ';') + return i; + break; + case 'e' : + case 'E' : + if (token == 'E') + return i; // if 'E' is outside "" and [] it must be the 'E' exponent + break; + default : break; + } + if ( i<0 ) + i--; + } + return -2; +} + +OUString SvNumberFormatter::GenerateFormat(sal_uInt32 nIndex, + LanguageType eLnge, + bool bThousand, + bool IsRed, + sal_uInt16 nPrecision, + sal_uInt16 nLeadingZeros) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if (eLnge == LANGUAGE_DONTKNOW) + { + eLnge = IniLnge; + } + + const SvNumberformat* pFormat = GetFormatEntry( nIndex ); + const SvNumFormatType eType = (pFormat ? pFormat->GetMaskedType() : SvNumFormatType::UNDEFINED); + + ImpGenerateCL(eLnge); // create new standard formats if necessary + + utl::DigitGroupingIterator aGrouping( xLocaleData->getDigitGrouping()); + // always group of 3 for Engineering notation + const sal_Int32 nDigitsInFirstGroup = ( bThousand && (eType == SvNumFormatType::SCIENTIFIC) ) ? 3 : aGrouping.get(); + const OUString& rThSep = GetNumThousandSep(); + + OUStringBuffer sString; + using comphelper::string::padToLength; + + if (eType & SvNumFormatType::TIME) + { + assert(pFormat && "with !pFormat eType can only be SvNumFormatType::UNDEFINED"); + sString = pFormat->GetFormatStringForTimePrecision( nPrecision ); + } + else if (nLeadingZeros == 0) + { + if (!bThousand) + sString.append('#'); + else + { + if (eType == SvNumFormatType::SCIENTIFIC) + { // for scientific, bThousand is used for Engineering notation + sString.append("###"); + } + else + { + sString.append('#'); + sString.append(rThSep); + padToLength(sString, sString.getLength() + nDigitsInFirstGroup, '#'); + } + } + } + else + { + for (sal_uInt16 i = 0; i < nLeadingZeros; i++) + { + if (bThousand && i > 0 && i == aGrouping.getPos()) + { + sString.insert(0, rThSep); + aGrouping.advance(); + } + sString.insert(0, '0'); + } + if ( bThousand ) + { + sal_Int32 nDigits = (eType == SvNumFormatType::SCIENTIFIC) ? 3*((nLeadingZeros-1)/3 + 1) : nDigitsInFirstGroup + 1; + for (sal_Int32 i = nLeadingZeros; i < nDigits; i++) + { + if ( i % nDigitsInFirstGroup == 0 ) + sString.insert(0, rThSep); + sString.insert(0, '#'); + } + } + } + if (nPrecision > 0 && eType != SvNumFormatType::FRACTION && !( eType & SvNumFormatType::TIME ) ) + { + sString.append(GetNumDecimalSep()); + padToLength(sString, sString.getLength() + nPrecision, '0'); + } + if (eType == SvNumFormatType::PERCENT) + { + sString.append( pFormat->GetPercentString() ); + } + else if (eType == SvNumFormatType::SCIENTIFIC) + { + OUStringBuffer sOldFormatString(pFormat->GetFormatstring()); + sal_Int32 nIndexE = ImpPosToken( sOldFormatString, 'E' ); + if (nIndexE > -1) + { + sal_Int32 nIndexSep = ImpPosToken( sOldFormatString, ';', nIndexE ); + if (nIndexSep > nIndexE) + sString.append( sOldFormatString.subView(nIndexE, nIndexSep - nIndexE) ); + else + sString.append( sOldFormatString.subView(nIndexE) ); + } + } + else if (eType == SvNumFormatType::CURRENCY) + { + OUStringBuffer sNegStr(sString); + OUString aCurr; + const NfCurrencyEntry* pEntry; + bool bBank; + if ( GetNewCurrencySymbolString( nIndex, aCurr, &pEntry, &bBank ) ) + { + if ( pEntry ) + { + sal_uInt16 nPosiForm = NfCurrencyEntry::GetEffectivePositiveFormat( + xLocaleData->getCurrPositiveFormat(), + pEntry->GetPositiveFormat(), bBank ); + sal_uInt16 nNegaForm = NfCurrencyEntry::GetEffectiveNegativeFormat( + xLocaleData->getCurrNegativeFormat(), + pEntry->GetNegativeFormat(), bBank ); + pEntry->CompletePositiveFormatString( sString, bBank, nPosiForm ); + pEntry->CompleteNegativeFormatString( sNegStr, bBank, nNegaForm ); + } + else + { // assume currency abbreviation (AKA banking symbol), not symbol + sal_uInt16 nPosiForm = NfCurrencyEntry::GetEffectivePositiveFormat( + xLocaleData->getCurrPositiveFormat(), + xLocaleData->getCurrPositiveFormat(), true ); + sal_uInt16 nNegaForm = NfCurrencyEntry::GetEffectiveNegativeFormat( + xLocaleData->getCurrNegativeFormat(), + xLocaleData->getCurrNegativeFormat(), true ); + NfCurrencyEntry::CompletePositiveFormatString( sString, aCurr, nPosiForm ); + NfCurrencyEntry::CompleteNegativeFormatString( sNegStr, aCurr, nNegaForm ); + } + } + else + { // "automatic" old style + OUString aSymbol, aAbbrev; + GetCompatibilityCurrency( aSymbol, aAbbrev ); + NfCurrencyEntry::CompletePositiveFormatString( sString, + aSymbol, xLocaleData->getCurrPositiveFormat() ); + NfCurrencyEntry::CompleteNegativeFormatString( sNegStr, + aSymbol, xLocaleData->getCurrNegativeFormat() ); + } + if (IsRed) + { + sString.append(';'); + sString.append('['); + sString.append(pFormatScanner->GetRedString()); + sString.append(']'); + } + else + { + sString.append(';'); + } + sString.append(sNegStr); + } + else if (eType == SvNumFormatType::FRACTION) + { + OUString aIntegerFractionDelimiterString = pFormat->GetIntegerFractionDelimiterString( 0 ); + if ( aIntegerFractionDelimiterString == " " ) + sString.append( aIntegerFractionDelimiterString ); + else + { + sString.append( '"' ); + sString.append( aIntegerFractionDelimiterString ); + sString.append( '"' ); + } + sString.append( pFormat->GetNumeratorString( 0 ) ); + sString.append( '/' ); + if ( nPrecision > 0 ) + padToLength(sString, sString.getLength() + nPrecision, '?'); + else + sString.append( '#' ); + } + if (eType != SvNumFormatType::CURRENCY) + { + bool insertBrackets = false; + if ( eType != SvNumFormatType::UNDEFINED) + { + insertBrackets = pFormat->IsNegativeInBracket(); + } + if (IsRed || insertBrackets) + { + OUStringBuffer sTmpStr(sString); + + if (pFormat && pFormat->HasPositiveBracketPlaceholder()) + { + sTmpStr.append('_'); + sTmpStr.append(')'); + } + sTmpStr.append(';'); + + if (IsRed) + { + sTmpStr.append('['); + sTmpStr.append(pFormatScanner->GetRedString()); + sTmpStr.append(']'); + } + + if (insertBrackets) + { + sTmpStr.append('('); + sTmpStr.append(sString); + sTmpStr.append(')'); + } + else + { + sTmpStr.append('-'); + sTmpStr.append(sString); + } + sString = sTmpStr; + } + } + return sString.makeStringAndClear(); +} + +bool SvNumberFormatter::IsUserDefined(sal_uInt32 F_Index) const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + const SvNumberformat* pFormat = GetFormatEntry(F_Index); + + return pFormat && (pFormat->GetType() & SvNumFormatType::DEFINED); +} + +bool SvNumberFormatter::IsUserDefined(std::u16string_view sStr, + LanguageType eLnge) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if (eLnge == LANGUAGE_DONTKNOW) + { + eLnge = IniLnge; + } + sal_uInt32 CLOffset = ImpGenerateCL(eLnge); // create new standard formats if necessary + eLnge = ActLnge; + + sal_uInt32 nKey = ImpIsEntry(sStr, CLOffset, eLnge); + if (nKey == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + return true; + } + SvNumberformat* pEntry = GetFormatEntry( nKey ); + return pEntry && (pEntry->GetType() & SvNumFormatType::DEFINED); +} + +sal_uInt32 SvNumberFormatter::GetEntryKey(std::u16string_view sStr, + LanguageType eLnge) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if (eLnge == LANGUAGE_DONTKNOW) + { + eLnge = IniLnge; + } + sal_uInt32 CLOffset = ImpGenerateCL(eLnge); // create new standard formats if necessary + return ImpIsEntry(sStr, CLOffset, eLnge); +} + +sal_uInt32 SvNumberFormatter::GetStandardIndex(LanguageType eLnge) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if (eLnge == LANGUAGE_DONTKNOW) + { + eLnge = IniLnge; + } + return GetStandardFormat(SvNumFormatType::NUMBER, eLnge); +} + +SvNumFormatType SvNumberFormatter::GetType(sal_uInt32 nFIndex) const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + SvNumFormatType eType; + const SvNumberformat* pFormat = GetFormatEntry( nFIndex ); + if (!pFormat) + { + eType = SvNumFormatType::UNDEFINED; + } + else + { + eType = pFormat->GetMaskedType(); + if (eType == SvNumFormatType::ALL) + { + eType = SvNumFormatType::DEFINED; + } + } + return eType; +} + +void SvNumberFormatter::ClearMergeTable() +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if ( pMergeTable ) + { + pMergeTable->clear(); + } +} + +SvNumberFormatterIndexTable* SvNumberFormatter::MergeFormatter(SvNumberFormatter& rTable) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if ( pMergeTable ) + { + ClearMergeTable(); + } + else + { + pMergeTable.reset( new SvNumberFormatterIndexTable ); + } + + sal_uInt32 nCLOffset = 0; + sal_uInt32 nOldKey, nOffset, nNewKey; + + for (const auto& rEntry : rTable.aFTable) + { + SvNumberformat* pFormat = rEntry.second.get(); + nOldKey = rEntry.first; + nOffset = nOldKey % SV_COUNTRY_LANGUAGE_OFFSET; // relative index + if (nOffset == 0) // 1st format of CL + { + nCLOffset = ImpGenerateCL(pFormat->GetLanguage()); + } + if (nOffset <= SV_MAX_COUNT_STANDARD_FORMATS) // Std.form. + { + nNewKey = nCLOffset + nOffset; + if (aFTable.find( nNewKey) == aFTable.end()) // not already present + { + std::unique_ptr pNewEntry(new SvNumberformat( *pFormat, *pFormatScanner )); + if (!aFTable.emplace( nNewKey, std::move(pNewEntry)).second) + { + SAL_WARN( "svl.numbers", "SvNumberFormatter::MergeFormatter: dup position"); + } + } + if (nNewKey != nOldKey) // new index + { + (*pMergeTable)[nOldKey] = nNewKey; + } + } + else // user defined + { + std::unique_ptr pNewEntry(new SvNumberformat( *pFormat, *pFormatScanner )); + nNewKey = ImpIsEntry(pNewEntry->GetFormatstring(), + nCLOffset, + pFormat->GetLanguage()); + if (nNewKey == NUMBERFORMAT_ENTRY_NOT_FOUND) // only if not present yet + { + SvNumberformat* pStdFormat = GetFormatEntry(nCLOffset + ZF_STANDARD); + sal_uInt32 nPos = nCLOffset + pStdFormat->GetLastInsertKey( SvNumberformat::FormatterPrivateAccess() ); + nNewKey = nPos+1; + if (nNewKey - nCLOffset >= SV_COUNTRY_LANGUAGE_OFFSET) + { + SAL_WARN( "svl.numbers", "SvNumberFormatter::MergeFormatter: too many formats for CL"); + } + else if (!aFTable.emplace( nNewKey, std::move(pNewEntry)).second) + { + SAL_WARN( "svl.numbers", "SvNumberFormatter::MergeFormatter: dup position"); + } + else + { + pStdFormat->SetLastInsertKey(static_cast(nNewKey - nCLOffset), + SvNumberformat::FormatterPrivateAccess()); + } + } + if (nNewKey != nOldKey) // new index + { + (*pMergeTable)[nOldKey] = nNewKey; + } + } + } + return pMergeTable.get(); +} + + +SvNumberFormatterMergeMap SvNumberFormatter::ConvertMergeTableToMap() +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if (!HasMergeFormatTable()) + { + return SvNumberFormatterMergeMap(); + } + SvNumberFormatterMergeMap aMap; + for (const auto& rEntry : *pMergeTable) + { + sal_uInt32 nOldKey = rEntry.first; + aMap[ nOldKey ] = rEntry.second; + } + ClearMergeTable(); + return aMap; +} + + +sal_uInt32 SvNumberFormatter::GetFormatForLanguageIfBuiltIn( sal_uInt32 nFormat, + LanguageType eLnge ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if ( eLnge == LANGUAGE_DONTKNOW ) + { + eLnge = IniLnge; + } + if ( nFormat < SV_COUNTRY_LANGUAGE_OFFSET && eLnge == IniLnge ) + { + return nFormat; // it stays as it is + } + sal_uInt32 nOffset = nFormat % SV_COUNTRY_LANGUAGE_OFFSET; // relative index + if ( nOffset > SV_MAX_COUNT_STANDARD_FORMATS ) + { + return nFormat; // not a built-in format + } + sal_uInt32 nCLOffset = ImpGenerateCL(eLnge); // create new standard formats if necessary + return nCLOffset + nOffset; +} + + +sal_uInt32 SvNumberFormatter::GetFormatIndex( NfIndexTableOffset nTabOff, + LanguageType eLnge ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if (nTabOff >= NF_INDEX_TABLE_ENTRIES) + return NUMBERFORMAT_ENTRY_NOT_FOUND; + + if (eLnge == LANGUAGE_DONTKNOW) + eLnge = IniLnge; + + if (indexTable[nTabOff] == NUMBERFORMAT_ENTRY_NOT_FOUND) + return NUMBERFORMAT_ENTRY_NOT_FOUND; + + sal_uInt32 nCLOffset = ImpGenerateCL(eLnge); // create new standard formats if necessary + + return nCLOffset + indexTable[nTabOff]; +} + + +NfIndexTableOffset SvNumberFormatter::GetIndexTableOffset( sal_uInt32 nFormat ) const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + sal_uInt32 nOffset = nFormat % SV_COUNTRY_LANGUAGE_OFFSET; // relative index + if ( nOffset > SV_MAX_COUNT_STANDARD_FORMATS ) + { + return NF_INDEX_TABLE_ENTRIES; // not a built-in format + } + + for ( sal_uInt16 j = 0; j < NF_INDEX_TABLE_ENTRIES; j++ ) + { + if (indexTable[j] == nOffset) + return static_cast(j); + } + return NF_INDEX_TABLE_ENTRIES; // bad luck +} + +void SvNumberFormatter::SetEvalDateFormat( NfEvalDateFormat eEDF ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + eEvalDateFormat = eEDF; +} + +NfEvalDateFormat SvNumberFormatter::GetEvalDateFormat() const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + return eEvalDateFormat; +} + +void SvNumberFormatter::SetYear2000( sal_uInt16 nVal ) +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + pStringScanner->SetYear2000( nVal ); +} + + +sal_uInt16 SvNumberFormatter::GetYear2000() const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + return pStringScanner->GetYear2000(); +} + + +sal_uInt16 SvNumberFormatter::ExpandTwoDigitYear( sal_uInt16 nYear ) const +{ + if ( nYear < 100 ) + return SvNumberFormatter::ExpandTwoDigitYear( nYear, + pStringScanner->GetYear2000() ); + return nYear; +} + + +// static +sal_uInt16 SvNumberFormatter::GetYear2000Default() +{ + if (!utl::ConfigManager::IsFuzzing()) + return officecfg::Office::Common::DateFormat::TwoDigitYear::get(); + return 1930; +} + +// static +void SvNumberFormatter::resetTheCurrencyTable() +{ + SAL_INFO("svl", "Resetting the currency table."); + + nSystemCurrencyPosition = 0; + bCurrencyTableInitialized = false; + + GetFormatterRegistry().ConfigurationChanged(nullptr, ConfigurationHints::Locale | ConfigurationHints::Currency | ConfigurationHints::DatePatterns); +} + +// static +const NfCurrencyTable& SvNumberFormatter::GetTheCurrencyTable() +{ + while ( !bCurrencyTableInitialized ) + ImpInitCurrencyTable(); + return theCurrencyTable(); +} + + +// static +const NfCurrencyEntry* SvNumberFormatter::MatchSystemCurrency() +{ + // MUST call GetTheCurrencyTable() before accessing nSystemCurrencyPosition + const NfCurrencyTable& rTable = GetTheCurrencyTable(); + return nSystemCurrencyPosition ? &rTable[nSystemCurrencyPosition] : nullptr; +} + + +// static +const NfCurrencyEntry& SvNumberFormatter::GetCurrencyEntry( LanguageType eLang ) +{ + if ( eLang == LANGUAGE_SYSTEM ) + { + const NfCurrencyEntry* pCurr = MatchSystemCurrency(); + return pCurr ? *pCurr : GetTheCurrencyTable()[0]; + } + else + { + eLang = MsLangId::getRealLanguage( eLang ); + const NfCurrencyTable& rTable = GetTheCurrencyTable(); + sal_uInt16 nCount = rTable.size(); + for ( sal_uInt16 j = 0; j < nCount; j++ ) + { + if ( rTable[j].GetLanguage() == eLang ) + return rTable[j]; + } + return rTable[0]; + } +} + + +// static +const NfCurrencyEntry* SvNumberFormatter::GetCurrencyEntry(std::u16string_view rAbbrev, LanguageType eLang ) +{ + eLang = MsLangId::getRealLanguage( eLang ); + const NfCurrencyTable& rTable = GetTheCurrencyTable(); + sal_uInt16 nCount = rTable.size(); + for ( sal_uInt16 j = 0; j < nCount; j++ ) + { + if ( rTable[j].GetLanguage() == eLang && + rTable[j].GetBankSymbol() == rAbbrev ) + { + return &rTable[j]; + } + } + return nullptr; +} + + +// static +const NfCurrencyEntry* SvNumberFormatter::GetLegacyOnlyCurrencyEntry( std::u16string_view rSymbol, + std::u16string_view rAbbrev ) +{ + GetTheCurrencyTable(); // just for initialization + const NfCurrencyTable& rTable = theLegacyOnlyCurrencyTable(); + sal_uInt16 nCount = rTable.size(); + for ( sal_uInt16 j = 0; j < nCount; j++ ) + { + if ( rTable[j].GetSymbol() == rSymbol && + rTable[j].GetBankSymbol() == rAbbrev ) + { + return &rTable[j]; + } + } + return nullptr; +} + + +// static +IMPL_STATIC_LINK_NOARG( SvNumberFormatter, CurrencyChangeLink, LinkParamNone*, void ) +{ + OUString aAbbrev; + LanguageType eLang = LANGUAGE_SYSTEM; + SvtSysLocaleOptions().GetCurrencyAbbrevAndLanguage( aAbbrev, eLang ); + SetDefaultSystemCurrency( aAbbrev, eLang ); +} + + +// static +void SvNumberFormatter::SetDefaultSystemCurrency( std::u16string_view rAbbrev, LanguageType eLang ) +{ + ::osl::MutexGuard aGuard( GetGlobalMutex() ); + if ( eLang == LANGUAGE_SYSTEM ) + { + eLang = SvtSysLocale().GetLanguageTag().getLanguageType(); + } + const NfCurrencyTable& rTable = GetTheCurrencyTable(); + sal_uInt16 nCount = rTable.size(); + if ( !rAbbrev.empty() ) + { + for ( sal_uInt16 j = 0; j < nCount; j++ ) + { + if ( rTable[j].GetLanguage() == eLang && rTable[j].GetBankSymbol() == rAbbrev ) + { + nSystemCurrencyPosition = j; + return ; + } + } + } + else + { + for ( sal_uInt16 j = 0; j < nCount; j++ ) + { + if ( rTable[j].GetLanguage() == eLang ) + { + nSystemCurrencyPosition = j; + return ; + } + } + } + nSystemCurrencyPosition = 0; // not found => simple SYSTEM +} + + +void SvNumberFormatter::ResetDefaultSystemCurrency() +{ + nDefaultSystemCurrencyFormat = NUMBERFORMAT_ENTRY_NOT_FOUND; +} + + +void SvNumberFormatter::InvalidateDateAcceptancePatterns() +{ + pStringScanner->InvalidateDateAcceptancePatterns(); +} + + +sal_uInt32 SvNumberFormatter::ImpGetDefaultSystemCurrencyFormat() +{ + if ( nDefaultSystemCurrencyFormat == NUMBERFORMAT_ENTRY_NOT_FOUND ) + { + sal_Int32 nCheck; + SvNumFormatType nType; + NfWSStringsDtor aCurrList; + sal_uInt16 nDefault = GetCurrencyFormatStrings( aCurrList, + GetCurrencyEntry( LANGUAGE_SYSTEM ), false ); + DBG_ASSERT( aCurrList.size(), "where is the NewCurrency System standard format?!?" ); + // if already loaded or user defined nDefaultSystemCurrencyFormat + // will be set to the right value + PutEntry( aCurrList[ nDefault ], nCheck, nType, + nDefaultSystemCurrencyFormat, LANGUAGE_SYSTEM ); + DBG_ASSERT( nCheck == 0, "NewCurrency CheckError" ); + DBG_ASSERT( nDefaultSystemCurrencyFormat != NUMBERFORMAT_ENTRY_NOT_FOUND, + "nDefaultSystemCurrencyFormat == NUMBERFORMAT_ENTRY_NOT_FOUND" ); + } + return nDefaultSystemCurrencyFormat; +} + + +sal_uInt32 SvNumberFormatter::ImpGetDefaultCurrencyFormat() +{ + sal_uInt32 CLOffset = ImpGetCLOffset( ActLnge ); + DefaultFormatKeysMap::const_iterator it = aDefaultFormatKeys.find( CLOffset + ZF_STANDARD_CURRENCY ); + sal_uInt32 nDefaultCurrencyFormat = (it != aDefaultFormatKeys.end() ? + it->second : NUMBERFORMAT_ENTRY_NOT_FOUND); + if ( nDefaultCurrencyFormat == NUMBERFORMAT_ENTRY_NOT_FOUND ) + { + // look for a defined standard + sal_uInt32 nStopKey = CLOffset + SV_COUNTRY_LANGUAGE_OFFSET; + sal_uInt32 nKey(0); + auto it2 = aFTable.lower_bound( CLOffset ); + while ( it2 != aFTable.end() && (nKey = it2->first) >= CLOffset && nKey < nStopKey ) + { + const SvNumberformat* pEntry = it2->second.get(); + if ( pEntry->IsStandard() && (pEntry->GetType() & SvNumFormatType::CURRENCY) ) + { + nDefaultCurrencyFormat = nKey; + break; // while + } + ++it2; + } + + if ( nDefaultCurrencyFormat == NUMBERFORMAT_ENTRY_NOT_FOUND ) + { // none found, create one + sal_Int32 nCheck; + NfWSStringsDtor aCurrList; + sal_uInt16 nDefault = GetCurrencyFormatStrings( aCurrList, + GetCurrencyEntry( ActLnge ), false ); + DBG_ASSERT( aCurrList.size(), "where is the NewCurrency standard format?" ); + if ( !aCurrList.empty() ) + { + // if already loaded or user defined nDefaultSystemCurrencyFormat + // will be set to the right value + SvNumFormatType nType; + PutEntry( aCurrList[ nDefault ], nCheck, nType, + nDefaultCurrencyFormat, ActLnge ); + DBG_ASSERT( nCheck == 0, "NewCurrency CheckError" ); + DBG_ASSERT( nDefaultCurrencyFormat != NUMBERFORMAT_ENTRY_NOT_FOUND, + "nDefaultCurrencyFormat == NUMBERFORMAT_ENTRY_NOT_FOUND" ); + } + // old automatic currency format as a last resort + if ( nDefaultCurrencyFormat == NUMBERFORMAT_ENTRY_NOT_FOUND ) + nDefaultCurrencyFormat = CLOffset + ZF_STANDARD_CURRENCY+3; + else + { // mark as standard so that it is found next time + SvNumberformat* pEntry = GetFormatEntry( nDefaultCurrencyFormat ); + if ( pEntry ) + pEntry->SetStandard(); + } + } + aDefaultFormatKeys[ CLOffset + ZF_STANDARD_CURRENCY ] = nDefaultCurrencyFormat; + } + return nDefaultCurrencyFormat; +} + + +// static +// true: continue; false: break loop, if pFoundEntry==NULL dupe found +bool SvNumberFormatter::ImpLookupCurrencyEntryLoopBody( + const NfCurrencyEntry*& pFoundEntry, bool& bFoundBank, const NfCurrencyEntry* pData, + sal_uInt16 nPos, std::u16string_view rSymbol ) +{ + bool bFound; + if ( pData->GetSymbol() == rSymbol ) + { + bFound = true; + bFoundBank = false; + } + else if ( pData->GetBankSymbol() == rSymbol ) + { + bFound = true; + bFoundBank = true; + } + else + bFound = false; + if ( bFound ) + { + if ( pFoundEntry && pFoundEntry != pData ) + { + pFoundEntry = nullptr; + return false; // break loop, not unique + } + if ( nPos == 0 ) + { // first entry is SYSTEM + pFoundEntry = MatchSystemCurrency(); + if ( pFoundEntry ) + { + return false; // break loop + // even if there are more matching entries + // this one is probably the one we are looking for + } + else + { + pFoundEntry = pData; + } + } + else + { + pFoundEntry = pData; + } + } + return true; +} + + +bool SvNumberFormatter::GetNewCurrencySymbolString( sal_uInt32 nFormat, OUString& rStr, + const NfCurrencyEntry** ppEntry /* = NULL */, + bool* pBank /* = NULL */ ) const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if ( ppEntry ) + *ppEntry = nullptr; + if ( pBank ) + *pBank = false; + + const SvNumberformat* pFormat = GetFormatEntry(nFormat); + if ( pFormat ) + { + OUString aSymbol, aExtension; + if ( pFormat->GetNewCurrencySymbol( aSymbol, aExtension ) ) + { + OUStringBuffer sBuff(128); // guess-estimate of a value that will pretty much guarantee no re-alloc + if ( ppEntry ) + { + bool bFoundBank = false; + // we definitely need an entry matching the format code string + const NfCurrencyEntry* pFoundEntry = GetCurrencyEntry( + bFoundBank, aSymbol, aExtension, pFormat->GetLanguage(), + true ); + if ( pFoundEntry ) + { + *ppEntry = pFoundEntry; + if ( pBank ) + *pBank = bFoundBank; + rStr = pFoundEntry->BuildSymbolString(bFoundBank); + } + } + if ( rStr.isEmpty() ) + { // analog to BuildSymbolString + sBuff.append("[$"); + if ( aSymbol.indexOf( '-' ) != -1 || + aSymbol.indexOf( ']' ) != -1 ) + { + sBuff.append('"'); + sBuff.append( aSymbol); + sBuff.append('"'); + } + else + { + sBuff.append(aSymbol); + } + if ( !aExtension.isEmpty() ) + { + sBuff.append(aExtension); + } + sBuff.append(']'); + } + rStr = sBuff.makeStringAndClear(); + return true; + } + } + rStr.clear(); + return false; +} + + +// static +const NfCurrencyEntry* SvNumberFormatter::GetCurrencyEntry( bool & bFoundBank, + std::u16string_view rSymbol, + const OUString& rExtension, + LanguageType eFormatLanguage, + bool bOnlyStringLanguage ) +{ + sal_Int32 nExtLen = rExtension.getLength(); + LanguageType eExtLang; + if ( nExtLen ) + { + // rExtension should be a 16-bit hex value max FFFF which may contain a + // leading "-" separator (that is not a minus sign, but toInt32 can be + // used to parse it, with post-processing as necessary): + sal_Int32 nExtLang = rExtension.toInt32( 16 ); + if ( !nExtLang ) + { + eExtLang = LANGUAGE_DONTKNOW; + } + else + { + eExtLang = LanguageType((nExtLang < 0) ? -nExtLang : nExtLang); + } + } + else + { + eExtLang = LANGUAGE_DONTKNOW; + } + const NfCurrencyEntry* pFoundEntry = nullptr; + const NfCurrencyTable& rTable = GetTheCurrencyTable(); + sal_uInt16 nCount = rTable.size(); + bool bCont = true; + + // first try with given extension language/country + if ( nExtLen ) + { + for ( sal_uInt16 j = 0; j < nCount && bCont; j++ ) + { + LanguageType eLang = rTable[j].GetLanguage(); + if ( eLang == eExtLang || + ((eExtLang == LANGUAGE_DONTKNOW) && + (eLang == LANGUAGE_SYSTEM))) + { + bCont = ImpLookupCurrencyEntryLoopBody( pFoundEntry, bFoundBank, + &rTable[j], j, rSymbol ); + } + } + } + + // ok? + if ( pFoundEntry || !bCont || (bOnlyStringLanguage && nExtLen) ) + { + return pFoundEntry; + } + if ( !bOnlyStringLanguage ) + { + // now try the language/country of the number format + for ( sal_uInt16 j = 0; j < nCount && bCont; j++ ) + { + LanguageType eLang = rTable[j].GetLanguage(); + if ( eLang == eFormatLanguage || + ((eFormatLanguage == LANGUAGE_DONTKNOW) && + (eLang == LANGUAGE_SYSTEM))) + { + bCont = ImpLookupCurrencyEntryLoopBody( pFoundEntry, bFoundBank, + &rTable[j], j, rSymbol ); + } + } + + // ok? + if ( pFoundEntry || !bCont ) + { + return pFoundEntry; + } + } + + // then try without language/country if no extension specified + if ( !nExtLen ) + { + for ( sal_uInt16 j = 0; j < nCount && bCont; j++ ) + { + bCont = ImpLookupCurrencyEntryLoopBody( pFoundEntry, bFoundBank, + &rTable[j], j, rSymbol ); + } + } + + return pFoundEntry; +} + + +void SvNumberFormatter::GetCompatibilityCurrency( OUString& rSymbol, OUString& rAbbrev ) const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + const css::uno::Sequence< css::i18n::Currency2 > + xCurrencies( xLocaleData->getAllCurrencies() ); + + auto pCurrency = std::find_if(xCurrencies.begin(), xCurrencies.end(), + [](const css::i18n::Currency2& rCurrency) { return rCurrency.UsedInCompatibleFormatCodes; }); + if (pCurrency != xCurrencies.end()) + { + rSymbol = pCurrency->Symbol; + rAbbrev = pCurrency->BankSymbol; + } + else + { + if (LocaleDataWrapper::areChecksEnabled()) + { + LocaleDataWrapper::outputCheckMessage( xLocaleData-> + appendLocaleInfo( u"GetCompatibilityCurrency: none?")); + } + rSymbol = xLocaleData->getCurrSymbol(); + rAbbrev = xLocaleData->getCurrBankSymbol(); + } +} + + +static void lcl_CheckCurrencySymbolPosition( const NfCurrencyEntry& rCurr ) +{ + switch ( rCurr.GetPositiveFormat() ) + { + case 0: // $1 + case 1: // 1$ + case 2: // $ 1 + case 3: // 1 $ + break; + default: + LocaleDataWrapper::outputCheckMessage( "lcl_CheckCurrencySymbolPosition: unknown PositiveFormat"); + break; + } + switch ( rCurr.GetNegativeFormat() ) + { + case 0: // ($1) + case 1: // -$1 + case 2: // $-1 + case 3: // $1- + case 4: // (1$) + case 5: // -1$ + case 6: // 1-$ + case 7: // 1$- + case 8: // -1 $ + case 9: // -$ 1 + case 10: // 1 $- + case 11: // $ -1 + case 12 : // $ 1- + case 13 : // 1- $ + case 14 : // ($ 1) + case 15 : // (1 $) + break; + default: + LocaleDataWrapper::outputCheckMessage( "lcl_CheckCurrencySymbolPosition: unknown NegativeFormat"); + break; + } +} + +// static +bool SvNumberFormatter::IsLocaleInstalled( LanguageType eLang ) +{ + // The set is initialized as a side effect of the currency table + // created, make sure that exists, which usually is the case unless a + // SvNumberFormatter was never instantiated. + GetTheCurrencyTable(); + return theInstalledLocales.find( eLang) != theInstalledLocales.end(); +} + +// static +void SvNumberFormatter::ImpInitCurrencyTable() +{ + // Race condition possible: + // ::osl::MutexGuard aGuard( GetMutex() ); + // while ( !bCurrencyTableInitialized ) + // ImpInitCurrencyTable(); + static bool bInitializing = false; + if ( bCurrencyTableInitialized || bInitializing ) + { + return ; + } + bInitializing = true; + + LanguageType eSysLang = SvtSysLocale().GetLanguageTag().getLanguageType(); + std::optional pLocaleData(std::in_place, + ::comphelper::getProcessComponentContext(), + SvtSysLocale().GetLanguageTag() ); + // get user configured currency + OUString aConfiguredCurrencyAbbrev; + LanguageType eConfiguredCurrencyLanguage = LANGUAGE_SYSTEM; + SvtSysLocaleOptions().GetCurrencyAbbrevAndLanguage( + aConfiguredCurrencyAbbrev, eConfiguredCurrencyLanguage ); + sal_uInt16 nSecondarySystemCurrencyPosition = 0; + sal_uInt16 nMatchingSystemCurrencyPosition = 0; + + // First entry is SYSTEM: + auto& rCurrencyTable = theCurrencyTable(); + rCurrencyTable.insert( + rCurrencyTable.begin(), + NfCurrencyEntry(*pLocaleData, LANGUAGE_SYSTEM)); + sal_uInt16 nCurrencyPos = 1; + + const css::uno::Sequence< css::lang::Locale > xLoc = LocaleDataWrapper::getInstalledLocaleNames(); + sal_Int32 nLocaleCount = xLoc.getLength(); + SAL_INFO( "svl.numbers", "number of locales: \"" << nLocaleCount << "\"" ); + NfCurrencyTable &rLegacyOnlyCurrencyTable = theLegacyOnlyCurrencyTable(); + sal_uInt16 nLegacyOnlyCurrencyPos = 0; + for ( css::lang::Locale const & rLocale : xLoc ) + { + LanguageType eLang = LanguageTag::convertToLanguageType( rLocale, false); + theInstalledLocales.insert( eLang); + pLocaleData.emplace( + ::comphelper::getProcessComponentContext(), + LanguageTag(rLocale) ); + Sequence< Currency2 > aCurrSeq = pLocaleData->getAllCurrencies(); + sal_Int32 nCurrencyCount = aCurrSeq.getLength(); + Currency2 const * const pCurrencies = aCurrSeq.getConstArray(); + + // one default currency for each locale, insert first so it is found first + sal_Int32 nDefault; + for ( nDefault = 0; nDefault < nCurrencyCount; nDefault++ ) + { + if ( pCurrencies[nDefault].Default ) + break; + } + std::optional pEntry; + if ( nDefault < nCurrencyCount ) + { + pEntry.emplace(pCurrencies[nDefault], *pLocaleData, eLang); + } + else + { // first or ShellsAndPebbles + pEntry.emplace(*pLocaleData, eLang); + } + if (LocaleDataWrapper::areChecksEnabled()) + { + lcl_CheckCurrencySymbolPosition( *pEntry ); + } + if ( !nSystemCurrencyPosition && !aConfiguredCurrencyAbbrev.isEmpty() && + pEntry->GetBankSymbol() == aConfiguredCurrencyAbbrev && + pEntry->GetLanguage() == eConfiguredCurrencyLanguage ) + { + nSystemCurrencyPosition = nCurrencyPos; + } + if ( !nMatchingSystemCurrencyPosition && + pEntry->GetLanguage() == eSysLang ) + { + nMatchingSystemCurrencyPosition = nCurrencyPos; + } + rCurrencyTable.insert( + rCurrencyTable.begin() + nCurrencyPos++, std::move(*pEntry)); + // all remaining currencies for each locale + if ( nCurrencyCount > 1 ) + { + sal_Int32 nCurrency; + for ( nCurrency = 0; nCurrency < nCurrencyCount; nCurrency++ ) + { + if (pCurrencies[nCurrency].LegacyOnly) + { + rLegacyOnlyCurrencyTable.insert( + rLegacyOnlyCurrencyTable.begin() + nLegacyOnlyCurrencyPos++, + NfCurrencyEntry( + pCurrencies[nCurrency], *pLocaleData, eLang)); + } + else if ( nCurrency != nDefault ) + { + pEntry.emplace(pCurrencies[nCurrency], *pLocaleData, eLang); + // no dupes + bool bInsert = true; + sal_uInt16 n = rCurrencyTable.size(); + sal_uInt16 aCurrencyIndex = 1; // skip first SYSTEM entry + for ( sal_uInt16 j=1; jGetBankSymbol() == aConfiguredCurrencyAbbrev : + pEntry->GetLanguage() == eConfiguredCurrencyLanguage) ) + { + nSecondarySystemCurrencyPosition = nCurrencyPos; + } + if ( !nMatchingSystemCurrencyPosition && + pEntry->GetLanguage() == eSysLang ) + { + nMatchingSystemCurrencyPosition = nCurrencyPos; + } + rCurrencyTable.insert( + rCurrencyTable.begin() + nCurrencyPos++, std::move(*pEntry)); + } + } + } + } + } + if ( !nSystemCurrencyPosition ) + { + nSystemCurrencyPosition = nSecondarySystemCurrencyPosition; + } + if ((!aConfiguredCurrencyAbbrev.isEmpty() && !nSystemCurrencyPosition) && + LocaleDataWrapper::areChecksEnabled()) + { + LocaleDataWrapper::outputCheckMessage( + "SvNumberFormatter::ImpInitCurrencyTable: configured currency not in I18N locale data."); + } + // match SYSTEM if no configured currency found + if ( !nSystemCurrencyPosition ) + { + nSystemCurrencyPosition = nMatchingSystemCurrencyPosition; + } + if ((aConfiguredCurrencyAbbrev.isEmpty() && !nSystemCurrencyPosition) && + LocaleDataWrapper::areChecksEnabled()) + { + LocaleDataWrapper::outputCheckMessage( + "SvNumberFormatter::ImpInitCurrencyTable: system currency not in I18N locale data."); + } + pLocaleData.reset(); + SvtSysLocaleOptions::SetCurrencyChangeLink( LINK( nullptr, SvNumberFormatter, CurrencyChangeLink ) ); + bInitializing = false; + bCurrencyTableInitialized = true; +} + + +static std::ptrdiff_t addToCurrencyFormatsList( NfWSStringsDtor& rStrArr, const OUString& rFormat ) +{ + // Prevent duplicates even over subsequent calls of + // GetCurrencyFormatStrings() with the same vector. + NfWSStringsDtor::const_iterator it( std::find( rStrArr.begin(), rStrArr.end(), rFormat)); + if (it != rStrArr.end()) + return it - rStrArr.begin(); + + rStrArr.push_back( rFormat); + return rStrArr.size() - 1; +} + + +sal_uInt16 SvNumberFormatter::GetCurrencyFormatStrings( NfWSStringsDtor& rStrArr, + const NfCurrencyEntry& rCurr, + bool bBank ) const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + OUString aRed = "[" + + pFormatScanner->GetRedString() + + "]"; + + sal_uInt16 nDefault = 0; + if ( bBank ) + { + // Only bank symbols. + OUString aPositiveBank = rCurr.BuildPositiveFormatString(true, *xLocaleData); + OUString aNegativeBank = rCurr.BuildNegativeFormatString(true, *xLocaleData ); + + OUString format1 = aPositiveBank + + ";" + + aNegativeBank; + addToCurrencyFormatsList( rStrArr, format1); + + OUString format2 = aPositiveBank + + ";" + + aRed + + aNegativeBank; + nDefault = addToCurrencyFormatsList( rStrArr, format2); + } + else + { + // Mixed formats like in SvNumberFormatter::ImpGenerateFormats() but no + // duplicates if no decimals in currency. + OUString aPositive = rCurr.BuildPositiveFormatString(false, *xLocaleData ); + OUString aNegative = rCurr.BuildNegativeFormatString(false, *xLocaleData ); + OUString format1; + OUString format2; + OUString format3; + OUString format4; + OUString format5; + if (rCurr.GetDigits()) + { + OUString aPositiveNoDec = rCurr.BuildPositiveFormatString(false, *xLocaleData, 0); + OUString aNegativeNoDec = rCurr.BuildNegativeFormatString(false, *xLocaleData, 0 ); + OUString aPositiveDashed = rCurr.BuildPositiveFormatString(false, *xLocaleData, 2); + OUString aNegativeDashed = rCurr.BuildNegativeFormatString(false, *xLocaleData, 2); + + format1 = aPositiveNoDec + + ";" + + aNegativeNoDec; + + format3 = aPositiveNoDec + + ";" + + aRed + + aNegativeNoDec; + + format5 = aPositiveDashed + + ";" + + aRed + + aNegativeDashed; + } + + format2 = aPositive + + ";" + + aNegative; + + format4 = aPositive + + ";" + + aRed + + aNegative; + + if (rCurr.GetDigits()) + { + addToCurrencyFormatsList( rStrArr, format1); + } + addToCurrencyFormatsList( rStrArr, format2); + if (rCurr.GetDigits()) + { + addToCurrencyFormatsList( rStrArr, format3); + } + nDefault = addToCurrencyFormatsList( rStrArr, format4); + if (rCurr.GetDigits()) + { + addToCurrencyFormatsList( rStrArr, format5); + } + } + return nDefault; +} + +sal_uInt32 SvNumberFormatter::GetMergeFormatIndex( sal_uInt32 nOldFmt ) const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + if (pMergeTable) + { + SvNumberFormatterIndexTable::const_iterator it = pMergeTable->find(nOldFmt); + if (it != pMergeTable->end()) + { + return it->second; + } + } + return nOldFmt; +} + +bool SvNumberFormatter::HasMergeFormatTable() const +{ + ::osl::MutexGuard aGuard( GetInstanceMutex() ); + return pMergeTable && !pMergeTable->empty(); +} + +// static +sal_uInt16 SvNumberFormatter::ExpandTwoDigitYear( sal_uInt16 nYear, sal_uInt16 nTwoDigitYearStart ) +{ + if ( nYear < 100 ) + { + if ( nYear < (nTwoDigitYearStart % 100) ) + { + return nYear + (((nTwoDigitYearStart / 100) + 1) * 100); + } + else + { + return nYear + ((nTwoDigitYearStart / 100) * 100); + } + } + return nYear; +} + +NfCurrencyEntry::NfCurrencyEntry( const LocaleDataWrapper& rLocaleData, LanguageType eLang ) +{ + aSymbol = rLocaleData.getCurrSymbol(); + aBankSymbol = rLocaleData.getCurrBankSymbol(); + eLanguage = eLang; + nPositiveFormat = rLocaleData.getCurrPositiveFormat(); + nNegativeFormat = rLocaleData.getCurrNegativeFormat(); + nDigits = rLocaleData.getCurrDigits(); + cZeroChar = rLocaleData.getCurrZeroChar(); +} + + +NfCurrencyEntry::NfCurrencyEntry( const css::i18n::Currency & rCurr, + const LocaleDataWrapper& rLocaleData, LanguageType eLang ) +{ + aSymbol = rCurr.Symbol; + aBankSymbol = rCurr.BankSymbol; + eLanguage = eLang; + nPositiveFormat = rLocaleData.getCurrPositiveFormat(); + nNegativeFormat = rLocaleData.getCurrNegativeFormat(); + nDigits = rCurr.DecimalPlaces; + cZeroChar = rLocaleData.getCurrZeroChar(); +} + +bool NfCurrencyEntry::operator==( const NfCurrencyEntry& r ) const +{ + return aSymbol == r.aSymbol + && aBankSymbol == r.aBankSymbol + && eLanguage == r.eLanguage + ; +} + +OUString NfCurrencyEntry::BuildSymbolString(bool bBank, + bool bWithoutExtension) const +{ + OUStringBuffer aBuf("[$"); + if (bBank) + { + aBuf.append(aBankSymbol); + } + else + { + if ( aSymbol.indexOf( '-' ) >= 0 || + aSymbol.indexOf( ']' ) >= 0) + { + aBuf.append("\"" + aSymbol + "\""); + } + else + { + aBuf.append(aSymbol); + } + if ( !bWithoutExtension && eLanguage != LANGUAGE_DONTKNOW && eLanguage != LANGUAGE_SYSTEM ) + { + sal_Int32 nLang = static_cast(eLanguage); + aBuf.append('-').append(OUString::number(nLang, 16).toAsciiUpperCase()); + } + } + aBuf.append(']'); + return aBuf.makeStringAndClear(); +} + +OUString NfCurrencyEntry::Impl_BuildFormatStringNumChars( const LocaleDataWrapper& rLoc, + sal_uInt16 nDecimalFormat) const +{ + OUStringBuffer aBuf; + aBuf.append("#" + rLoc.getNumThousandSep() + "##0"); + if (nDecimalFormat && nDigits) + { + aBuf.append(rLoc.getNumDecimalSep()); + sal_Unicode cDecimalChar = nDecimalFormat == 2 ? '-' : cZeroChar; + for (sal_uInt16 i = 0; i < nDigits; ++i) + { + aBuf.append(cDecimalChar); + } + } + return aBuf.makeStringAndClear(); +} + + +OUString NfCurrencyEntry::BuildPositiveFormatString(bool bBank, const LocaleDataWrapper& rLoc, + sal_uInt16 nDecimalFormat) const +{ + OUStringBuffer sBuf(Impl_BuildFormatStringNumChars(rLoc, nDecimalFormat)); + sal_uInt16 nPosiForm = NfCurrencyEntry::GetEffectivePositiveFormat( rLoc.getCurrPositiveFormat(), + nPositiveFormat, bBank ); + CompletePositiveFormatString(sBuf, bBank, nPosiForm); + return sBuf.makeStringAndClear(); +} + + +OUString NfCurrencyEntry::BuildNegativeFormatString(bool bBank, + const LocaleDataWrapper& rLoc, sal_uInt16 nDecimalFormat ) const +{ + OUStringBuffer sBuf(Impl_BuildFormatStringNumChars(rLoc, nDecimalFormat)); + sal_uInt16 nNegaForm = NfCurrencyEntry::GetEffectiveNegativeFormat( rLoc.getCurrNegativeFormat(), + nNegativeFormat, bBank ); + CompleteNegativeFormatString(sBuf, bBank, nNegaForm); + return sBuf.makeStringAndClear(); +} + + +void NfCurrencyEntry::CompletePositiveFormatString(OUStringBuffer& rStr, bool bBank, + sal_uInt16 nPosiForm) const +{ + OUString aSymStr = BuildSymbolString(bBank); + NfCurrencyEntry::CompletePositiveFormatString( rStr, aSymStr, nPosiForm ); +} + + +void NfCurrencyEntry::CompleteNegativeFormatString(OUStringBuffer& rStr, bool bBank, + sal_uInt16 nNegaForm) const +{ + OUString aSymStr = BuildSymbolString(bBank); + NfCurrencyEntry::CompleteNegativeFormatString( rStr, aSymStr, nNegaForm ); +} + + +// static +void NfCurrencyEntry::CompletePositiveFormatString(OUStringBuffer& rStr, std::u16string_view rSymStr, + sal_uInt16 nPositiveFormat) +{ + switch( nPositiveFormat ) + { + case 0: // $1 + rStr.insert(0, rSymStr); + break; + case 1: // 1$ + rStr.append(rSymStr); + break; + case 2: // $ 1 + { + rStr.insert(0, ' '); + rStr.insert(0, rSymStr); + } + break; + case 3: // 1 $ + { + rStr.append(' '); + rStr.append(rSymStr); + } + break; + default: + SAL_WARN( "svl.numbers", "NfCurrencyEntry::CompletePositiveFormatString: unknown option"); + break; + } +} + + +// static +void NfCurrencyEntry::CompleteNegativeFormatString(OUStringBuffer& rStr, + std::u16string_view rSymStr, + sal_uInt16 nNegativeFormat) +{ + switch( nNegativeFormat ) + { + case 0: // ($1) + { + rStr.insert(0, rSymStr); + rStr.insert(0, '('); + rStr.append(')'); + } + break; + case 1: // -$1 + { + rStr.insert(0, rSymStr); + rStr.insert(0, '-'); + } + break; + case 2: // $-1 + { + rStr.insert(0, '-'); + rStr.insert(0, rSymStr); + } + break; + case 3: // $1- + { + rStr.insert(0, rSymStr); + rStr.append('-'); + } + break; + case 4: // (1$) + { + rStr.insert(0, '('); + rStr.append(rSymStr); + rStr.append(')'); + } + break; + case 5: // -1$ + { + rStr.append(rSymStr); + rStr.insert(0, '-'); + } + break; + case 6: // 1-$ + { + rStr.append('-'); + rStr.append(rSymStr); + } + break; + case 7: // 1$- + { + rStr.append(rSymStr); + rStr.append('-'); + } + break; + case 8: // -1 $ + { + rStr.append(' '); + rStr.append(rSymStr); + rStr.insert(0, '-'); + } + break; + case 9: // -$ 1 + { + rStr.insert(0, ' '); + rStr.insert(0, rSymStr); + rStr.insert(0, '-'); + } + break; + case 10: // 1 $- + { + rStr.append(' '); + rStr.append(rSymStr); + rStr.append('-'); + } + break; + case 11: // $ -1 + { + rStr.insert(0, " -"); + rStr.insert(0, rSymStr); + } + break; + case 12 : // $ 1- + { + rStr.insert(0, ' '); + rStr.insert(0, rSymStr); + rStr.append('-'); + } + break; + case 13 : // 1- $ + { + rStr.append('-'); + rStr.append(' '); + rStr.append(rSymStr); + } + break; + case 14 : // ($ 1) + { + rStr.insert(0, ' '); + rStr.insert(0, rSymStr); + rStr.insert(0, '('); + rStr.append(')'); + } + break; + case 15 : // (1 $) + { + rStr.insert(0, '('); + rStr.append(' '); + rStr.append(rSymStr); + rStr.append(')'); + } + break; + default: + SAL_WARN( "svl.numbers", "NfCurrencyEntry::CompleteNegativeFormatString: unknown option"); + break; + } +} + + +// static +sal_uInt16 NfCurrencyEntry::GetEffectivePositiveFormat( sal_uInt16 nIntlFormat, + sal_uInt16 nCurrFormat, bool bBank ) +{ + if ( bBank ) + { +#if NF_BANKSYMBOL_FIX_POSITION + (void) nIntlFormat; // avoid warnings + return 3; +#else + switch ( nIntlFormat ) + { + case 0: // $1 + nIntlFormat = 2; // $ 1 + break; + case 1: // 1$ + nIntlFormat = 3; // 1 $ + break; + case 2: // $ 1 + break; + case 3: // 1 $ + break; + default: + SAL_WARN( "svl.numbers", "NfCurrencyEntry::GetEffectivePositiveFormat: unknown option"); + break; + } + return nIntlFormat; +#endif + } + else + return nCurrFormat; +} + + +//! Call this only if nCurrFormat is really with parentheses! +static sal_uInt16 lcl_MergeNegativeParenthesisFormat( sal_uInt16 nIntlFormat, sal_uInt16 nCurrFormat ) +{ + short nSign = 0; // -1:=bracket 0:=left, 1:=middle, 2:=right + switch ( nIntlFormat ) + { + case 0: // ($1) + case 4: // (1$) + case 14 : // ($ 1) + case 15 : // (1 $) + return nCurrFormat; + case 1: // -$1 + case 5: // -1$ + case 8: // -1 $ + case 9: // -$ 1 + nSign = 0; + break; + case 2: // $-1 + case 6: // 1-$ + case 11 : // $ -1 + case 13 : // 1- $ + nSign = 1; + break; + case 3: // $1- + case 7: // 1$- + case 10: // 1 $- + case 12 : // $ 1- + nSign = 2; + break; + default: + SAL_WARN( "svl.numbers", "lcl_MergeNegativeParenthesisFormat: unknown option"); + break; + } + + switch ( nCurrFormat ) + { + case 0: // ($1) + switch ( nSign ) + { + case 0: + return 1; // -$1 + case 1: + return 2; // $-1 + case 2: + return 3; // $1- + } + break; + case 4: // (1$) + switch ( nSign ) + { + case 0: + return 5; // -1$ + case 1: + return 6; // 1-$ + case 2: + return 7; // 1$- + } + break; + case 14 : // ($ 1) + switch ( nSign ) + { + case 0: + return 9; // -$ 1 + case 1: + return 11; // $ -1 + case 2: + return 12; // $ 1- + } + break; + case 15 : // (1 $) + switch ( nSign ) + { + case 0: + return 8; // -1 $ + case 1: + return 13; // 1- $ + case 2: + return 10; // 1 $- + } + break; + } + return nCurrFormat; +} + + +// static +sal_uInt16 NfCurrencyEntry::GetEffectiveNegativeFormat( sal_uInt16 nIntlFormat, + sal_uInt16 nCurrFormat, bool bBank ) +{ + if ( bBank ) + { +#if NF_BANKSYMBOL_FIX_POSITION + return 8; +#else + switch ( nIntlFormat ) + { + case 0: // ($1) +// nIntlFormat = 14; // ($ 1) + nIntlFormat = 9; // -$ 1 + break; + case 1: // -$1 + nIntlFormat = 9; // -$ 1 + break; + case 2: // $-1 + nIntlFormat = 11; // $ -1 + break; + case 3: // $1- + nIntlFormat = 12; // $ 1- + break; + case 4: // (1$) +// nIntlFormat = 15; // (1 $) + nIntlFormat = 8; // -1 $ + break; + case 5: // -1$ + nIntlFormat = 8; // -1 $ + break; + case 6: // 1-$ + nIntlFormat = 13; // 1- $ + break; + case 7: // 1$- + nIntlFormat = 10; // 1 $- + break; + case 8: // -1 $ + break; + case 9: // -$ 1 + break; + case 10: // 1 $- + break; + case 11: // $ -1 + break; + case 12 : // $ 1- + break; + case 13 : // 1- $ + break; + case 14 : // ($ 1) +// nIntlFormat = 14; // ($ 1) + nIntlFormat = 9; // -$ 1 + break; + case 15 : // (1 $) +// nIntlFormat = 15; // (1 $) + nIntlFormat = 8; // -1 $ + break; + default: + SAL_WARN( "svl.numbers", "NfCurrencyEntry::GetEffectiveNegativeFormat: unknown option"); + break; + } +#endif + } + else if ( nIntlFormat != nCurrFormat ) + { + switch ( nCurrFormat ) + { + case 0: // ($1) + nIntlFormat = lcl_MergeNegativeParenthesisFormat( + nIntlFormat, nCurrFormat ); + break; + case 1: // -$1 + nIntlFormat = nCurrFormat; + break; + case 2: // $-1 + nIntlFormat = nCurrFormat; + break; + case 3: // $1- + nIntlFormat = nCurrFormat; + break; + case 4: // (1$) + nIntlFormat = lcl_MergeNegativeParenthesisFormat( + nIntlFormat, nCurrFormat ); + break; + case 5: // -1$ + nIntlFormat = nCurrFormat; + break; + case 6: // 1-$ + nIntlFormat = nCurrFormat; + break; + case 7: // 1$- + nIntlFormat = nCurrFormat; + break; + case 8: // -1 $ + nIntlFormat = nCurrFormat; + break; + case 9: // -$ 1 + nIntlFormat = nCurrFormat; + break; + case 10: // 1 $- + nIntlFormat = nCurrFormat; + break; + case 11: // $ -1 + nIntlFormat = nCurrFormat; + break; + case 12 : // $ 1- + nIntlFormat = nCurrFormat; + break; + case 13 : // 1- $ + nIntlFormat = nCurrFormat; + break; + case 14 : // ($ 1) + nIntlFormat = lcl_MergeNegativeParenthesisFormat( + nIntlFormat, nCurrFormat ); + break; + case 15 : // (1 $) + nIntlFormat = lcl_MergeNegativeParenthesisFormat( + nIntlFormat, nCurrFormat ); + break; + default: + SAL_WARN( "svl.numbers", "NfCurrencyEntry::GetEffectiveNegativeFormat: unknown option"); + break; + } + } + return nIntlFormat; +} + +const NfKeywordTable & SvNumberFormatter::GetKeywords( sal_uInt32 nKey ) +{ + osl::MutexGuard aGuard( GetInstanceMutex() ); + const SvNumberformat* pFormat = GetFormatEntry( nKey); + if (pFormat) + ChangeIntl( pFormat->GetLanguage()); + else + ChangeIntl( IniLnge); + return pFormatScanner->GetKeywords(); +} + +const NfKeywordTable & SvNumberFormatter::GetEnglishKeywords() const +{ + return ImpSvNumberformatScan::GetEnglishKeywords(); +} + +const std::vector & SvNumberFormatter::GetStandardColors() const +{ + return ImpSvNumberformatScan::GetStandardColors(); +} + +size_t SvNumberFormatter::GetMaxDefaultColors() const +{ + return ImpSvNumberformatScan::GetMaxDefaultColors(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/numbers/zformat.cxx b/svl/source/numbers/zformat.cxx new file mode 100644 index 000000000..0ddd6e084 --- /dev/null +++ b/svl/source/numbers/zformat.cxx @@ -0,0 +1,6035 @@ +/* -*- 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "zforscan.hxx" + +#include "zforfind.hxx" +#include +#include +#include + +#include +#include + +using namespace svt; + +namespace { + +constexpr OUStringLiteral GREGORIAN = u"gregorian"; + +const sal_uInt16 UPPER_PRECISION = 300; // entirely arbitrary... +const double EXP_LOWER_BOUND = 1.0E-4; // prefer scientific notation below this value. +const double EXP_ABS_UPPER_BOUND = 1.0E15; // use exponential notation above that absolute value. + // Back in time was E16 that lead + // to display rounding errors, see + // also sal/rtl/math.cxx + // doubleToString() + +} // namespace + +const double D_MAX_U_INT32 = double(0xffffffff); // 4294967295.0 +constexpr double D_MAX_INTEGER = (sal_uInt64(1) << 53) - 1; + +const double D_MAX_D_BY_100 = 1.7E306; +const double D_MIN_M_BY_1000 = 2.3E-305; + +const sal_uInt8 cCharWidths[ 128-32 ] = { + 1,1,1,2,2,3,2,1,1,1,1,2,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,2, + 3,2,2,2,2,2,2,3,2,1,2,2,2,3,3,3, + 2,3,2,2,2,2,2,3,2,2,2,1,1,1,2,2, + 1,2,2,2,2,2,1,2,2,1,1,2,1,3,2,2, + 2,2,1,2,1,2,2,2,2,2,2,1,1,1,2,1 +}; + +// static +sal_Int32 SvNumberformat::InsertBlanks( OUStringBuffer& r, sal_Int32 nPos, sal_Unicode c ) +{ + if( c >= 32 ) + { + int n = 2; // Default for chars > 128 (HACK!) + if( c <= 127 ) + { + n = static_cast(cCharWidths[ c - 32 ]); + } + while( n-- ) + { + r.insert( nPos++, ' '); + } + } + return nPos; +} + +static tools::Long GetPrecExp( double fAbsVal ) +{ + DBG_ASSERT( fAbsVal > 0.0, "GetPrecExp: fAbsVal <= 0.0" ); + if ( fAbsVal < 1e-7 || fAbsVal > 1e7 ) + { + // Shear: whether it's faster or not, falls in between 1e6 and 1e7 + return static_cast(floor( log10( fAbsVal ) )) + 1; + } + else + { + tools::Long nPrecExp = 1; + while( fAbsVal < 1 ) + { + fAbsVal *= 10; + nPrecExp--; + } + while( fAbsVal >= 10 ) + { + fAbsVal /= 10; + nPrecExp++; + } + return nPrecExp; + } +} + +/** + * SvNumberformatInfo + * */ + +void ImpSvNumberformatInfo::Copy( const ImpSvNumberformatInfo& rNumFor, sal_uInt16 nCnt ) +{ + for (sal_uInt16 i = 0; i < nCnt; ++i) + { + sStrArray[i] = rNumFor.sStrArray[i]; + nTypeArray[i] = rNumFor.nTypeArray[i]; + } + eScannedType = rNumFor.eScannedType; + bThousand = rNumFor.bThousand; + nThousand = rNumFor.nThousand; + nCntPre = rNumFor.nCntPre; + nCntPost = rNumFor.nCntPost; + nCntExp = rNumFor.nCntExp; +} + +const std::map> tblDBNumToNatNum + = { { primary(LANGUAGE_CHINESE), { 4, 5, 3, 0 } }, + { primary(LANGUAGE_JAPANESE), { 4, 5, 3, 0 } }, + { primary(LANGUAGE_KOREAN), { 4, 5, 6, 10 } } }; + +// static +sal_uInt8 SvNumberNatNum::MapDBNumToNatNum( sal_uInt8 nDBNum, LanguageType eLang, bool bDate ) +{ + sal_uInt8 nNatNum = 0; + eLang = MsLangId::getRealLanguage( eLang ); // resolve SYSTEM etc. + eLang = primary(eLang); // 10 bit primary language + if ( bDate ) + { + if ( nDBNum == 4 && eLang == primary(LANGUAGE_KOREAN) ) + { + nNatNum = 10; + } + else if ( nDBNum <= 3 ) + { + nNatNum = nDBNum; // known to be good for: zh,ja,ko / 1,2,3 + } + } + else + { + if (1 <= nDBNum && nDBNum <= 4) + { + auto const it = tblDBNumToNatNum.find(eLang); + if (it != tblDBNumToNatNum.end()) + nNatNum = it->second[nDBNum - 1]; + + } + } + return nNatNum; +} + +const std::map> tblNatNumToDBNum + = { { primary(LANGUAGE_CHINESE), { 1, 0, 0, 1, 2, 3, 0, 0, 0 } }, + { primary(LANGUAGE_JAPANESE), { 1, 2, 3, 1, 2, 3, 1, 2, 0 } }, + { primary(LANGUAGE_KOREAN), { 1, 2, 3, 1, 2, 3, 1, 2, 4 } } }; + +// static +sal_uInt8 SvNumberNatNum::MapNatNumToDBNum( sal_uInt8 nNatNum, LanguageType eLang, bool bDate ) +{ + sal_uInt8 nDBNum = 0; + eLang = MsLangId::getRealLanguage( eLang ); // resolve SYSTEM etc. + eLang = primary(eLang); // 10 bit primary language + if ( bDate ) + { + if ( nNatNum == 10 && eLang == primary(LANGUAGE_KOREAN) ) + { + nDBNum = 4; + } + else if ( nNatNum <= 3 ) + { + nDBNum = nNatNum; // known to be good for: zh,ja,ko / 1,2,3 + } + } + else + { + if (1 <= nNatNum && nNatNum <= 9) + { + auto const it = tblNatNumToDBNum.find(eLang); + if (it != tblNatNumToDBNum.end()) + nDBNum = it->second[nNatNum - 1]; + } + } + return nDBNum; +} + +/** + * SvNumFor + */ + +ImpSvNumFor::ImpSvNumFor() +{ + nStringsCnt = 0; + aI.eScannedType = SvNumFormatType::UNDEFINED; + aI.bThousand = false; + aI.nThousand = 0; + aI.nCntPre = 0; + aI.nCntPost = 0; + aI.nCntExp = 0; + pColor = nullptr; +} + +ImpSvNumFor::~ImpSvNumFor() +{ +} + +void ImpSvNumFor::Enlarge(sal_uInt16 nCnt) +{ + if ( nStringsCnt != nCnt ) + { + nStringsCnt = nCnt; + aI.nTypeArray.resize(nCnt); + aI.sStrArray.resize(nCnt); + } +} + +void ImpSvNumFor::Copy( const ImpSvNumFor& rNumFor, const ImpSvNumberformatScan* pSc ) +{ + Enlarge( rNumFor.nStringsCnt ); + aI.Copy( rNumFor.aI, nStringsCnt ); + sColorName = rNumFor.sColorName; + if ( pSc ) + { + pColor = pSc->GetColor( sColorName ); // #121103# don't copy pointer between documents + } + else + { + pColor = rNumFor.pColor; + } + aNatNum = rNumFor.aNatNum; +} + +bool ImpSvNumFor::HasNewCurrency() const +{ + for ( sal_uInt16 j=0; j 0 ) + { + return true; // conditions + } + switch ( nSymbolType ) + { + case BRACKET_SYMBOLTYPE_COLOR : + case BRACKET_SYMBOLTYPE_DBNUM1 : + case BRACKET_SYMBOLTYPE_DBNUM2 : + case BRACKET_SYMBOLTYPE_DBNUM3 : + case BRACKET_SYMBOLTYPE_DBNUM4 : + case BRACKET_SYMBOLTYPE_DBNUM5 : + case BRACKET_SYMBOLTYPE_DBNUM6 : + case BRACKET_SYMBOLTYPE_DBNUM7 : + case BRACKET_SYMBOLTYPE_DBNUM8 : + case BRACKET_SYMBOLTYPE_DBNUM9 : + case BRACKET_SYMBOLTYPE_LOCALE : + case BRACKET_SYMBOLTYPE_NATNUM0 : + case BRACKET_SYMBOLTYPE_NATNUM1 : + case BRACKET_SYMBOLTYPE_NATNUM2 : + case BRACKET_SYMBOLTYPE_NATNUM3 : + case BRACKET_SYMBOLTYPE_NATNUM4 : + case BRACKET_SYMBOLTYPE_NATNUM5 : + case BRACKET_SYMBOLTYPE_NATNUM6 : + case BRACKET_SYMBOLTYPE_NATNUM7 : + case BRACKET_SYMBOLTYPE_NATNUM8 : + case BRACKET_SYMBOLTYPE_NATNUM9 : + case BRACKET_SYMBOLTYPE_NATNUM10 : + case BRACKET_SYMBOLTYPE_NATNUM11 : + case BRACKET_SYMBOLTYPE_NATNUM12 : + case BRACKET_SYMBOLTYPE_NATNUM13 : + case BRACKET_SYMBOLTYPE_NATNUM14 : + case BRACKET_SYMBOLTYPE_NATNUM15 : + case BRACKET_SYMBOLTYPE_NATNUM16 : + case BRACKET_SYMBOLTYPE_NATNUM17 : + case BRACKET_SYMBOLTYPE_NATNUM18 : + case BRACKET_SYMBOLTYPE_NATNUM19 : + return true; + } + return false; +} + +/** Import extended LCID from Excel + */ +OUString SvNumberformat::ImpObtainCalendarAndNumerals( OUStringBuffer& rString, sal_Int32 nPos, + LanguageType& nLang, const LocaleType& aTmpLocale ) +{ + OUString sCalendar; + sal_uInt16 nNatNum = 0; + LanguageType nLocaleLang = MsLangId::getRealLanguage( maLocale.meLanguage ); + LanguageType nTmpLocaleLang = MsLangId::getRealLanguage( aTmpLocale.meLanguage ); + /* NOTE: enhancement to allow other possible locale dependent + * calendars and numerals. BUT only if our locale data allows it! For LCID + * numerals and calendars see + * http://office.microsoft.com/en-us/excel/HA010346351033.aspx + * Calendar is inserted after + * all prefixes have been consumed as it is actually a format modifier + * and not a prefix. + * Currently calendars are tied to the locale of the entire number + * format, e.g. [~buddhist] in en_US doesn't work. + * => Having different locales in sub formats does not work! + * */ + /* TODO: calendars could be tied to a sub format's NatNum info + * instead, or even better be available for any locale. Needs a + * different implementation of GetCal() and locale data calendars. + * */ + switch ( aTmpLocale.mnCalendarType & 0x7F ) + { + case 0x03 : // Gengou calendar + // Only Japanese language support Gengou calendar. + // It is an implicit "other" calendar where E, EE, R and RR + // automatically switch to and YY and YYYY switch to Gregorian. Do + // not add the "[~gengou]" modifier. + if ( nLocaleLang != LANGUAGE_JAPANESE ) + { + nLang = maLocale.meLanguage = LANGUAGE_JAPANESE; + } + break; + case 0x05 : // Korean Dangi calendar + sCalendar = "[~dangi]"; + // Only Korean language support dangi calendar + if ( nLocaleLang != LANGUAGE_KOREAN ) + { + nLang = maLocale.meLanguage = LANGUAGE_KOREAN; + } + break; + case 0x06 : // Hijri calendar + case 0x17 : // same? + sCalendar = "[~hijri]"; + // Only Arabic or Farsi languages support Hijri calendar + if ( ( primary( nLocaleLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY ) + && nLocaleLang != LANGUAGE_FARSI ) + { + if ( ( primary( nTmpLocaleLang ) == LANGUAGE_ARABIC_PRIMARY_ONLY ) + || nTmpLocaleLang == LANGUAGE_FARSI ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = LANGUAGE_ARABIC_SAUDI_ARABIA; + } + } + break; + case 0x07 : // Buddhist calendar + sCalendar="[~buddhist]"; + // Only Thai or Lao languages support Buddhist calendar + if ( nLocaleLang != LANGUAGE_THAI && nLocaleLang != LANGUAGE_LAO ) + { + if ( nTmpLocaleLang == LANGUAGE_THAI || nTmpLocaleLang == LANGUAGE_LAO ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = LANGUAGE_THAI; + } + } + break; + case 0x08 : // Hebrew calendar + sCalendar = "[~jewish]"; + // Many languages (but not all) support Jewish calendar + // Unable to find any logic => keep same language + break; + case 0x0E : // unknown calendar + case 0x0F : // unknown calendar + case 0x10 : // Indian calendar (unsupported) + case 0x11 : // unknown calendar + case 0x12 : // unknown calendar + case 0x13 : // unknown calendar + default : // other calendars (see tdf#36038) are not handle by LibO + break; + } + /** Reference language for each numeral ID */ + static const LanguageType aNumeralIDtoLanguage []= + { + LANGUAGE_DONTKNOW, // 0x00 + LANGUAGE_ENGLISH_US, // 0x01 + LANGUAGE_ARABIC_SAUDI_ARABIA, // 0x02 + all Arabic + LANGUAGE_FARSI, // 0x03 + LANGUAGE_HINDI, // 0x04 + Devanagari + LANGUAGE_BENGALI, // 0x05 + LANGUAGE_PUNJABI, // 0x06 + LANGUAGE_GUJARATI, // 0x07 + LANGUAGE_ODIA, // 0x08 + LANGUAGE_TAMIL, // 0x09 + LANGUAGE_TELUGU, // 0x0A + LANGUAGE_KANNADA, // 0x0B + LANGUAGE_MALAYALAM, // 0x0C + LANGUAGE_THAI, // 0x0D + LANGUAGE_LAO, // 0x0E + LANGUAGE_TIBETAN, // 0x0F + LANGUAGE_BURMESE, // 0x10 + LANGUAGE_TIGRIGNA_ETHIOPIA, // 0x11 + LANGUAGE_KHMER, // 0x12 + LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA, // 0x13 + LANGUAGE_DONTKNOW, // 0x14 + LANGUAGE_DONTKNOW, // 0x15 + LANGUAGE_DONTKNOW, // 0x16 + LANGUAGE_DONTKNOW, // 0x17 + LANGUAGE_DONTKNOW, // 0x18 + LANGUAGE_DONTKNOW, // 0x19 + LANGUAGE_DONTKNOW, // 0x1A + LANGUAGE_JAPANESE, // 0x1B + LANGUAGE_JAPANESE, // 0x1C + LANGUAGE_JAPANESE, // 0x1D + LANGUAGE_CHINESE_SIMPLIFIED, // 0x1E + LANGUAGE_CHINESE_SIMPLIFIED, // 0x1F + LANGUAGE_CHINESE_SIMPLIFIED, // 0x20 + LANGUAGE_CHINESE_TRADITIONAL, // 0x21 + LANGUAGE_CHINESE_TRADITIONAL, // 0x22 + LANGUAGE_CHINESE_TRADITIONAL, // 0x23 + LANGUAGE_KOREAN, // 0x24 + LANGUAGE_KOREAN, // 0x25 + LANGUAGE_KOREAN, // 0x26 + LANGUAGE_KOREAN // 0x27 + }; + + sal_uInt16 nNumeralID = aTmpLocale.mnNumeralShape & 0x7F; + LanguageType nReferenceLanguage = nNumeralID <= 0x27 ? aNumeralIDtoLanguage[nNumeralID] : LANGUAGE_DONTKNOW; + + switch ( nNumeralID ) + { + // Regular cases: all languages with same primary mask use same numerals + case 0x03 : // Perso-Arabic (Farsi) numerals + case 0x05 : // Bengali numerals + case 0x06 : // Punjabi numerals + case 0x07 : // Gujarati numerals + case 0x08 : // Odia (Orya) numerals + case 0x09 : // Tamil numerals + case 0x0A : // Telugu numerals + case 0x0B : // Kannada numerals + case 0x0C : // Malayalam numerals + case 0x0D : // Thai numerals + case 0x0E : // Lao numerals + case 0x0F : // Tibetan numerals + case 0x10 : // Burmese (Myanmar) numerals + case 0x11 : // Tigrigna (Ethiopia) numerals + case 0x12 : // Khmer numerals + if ( primary( nLocaleLang ) != primary( nReferenceLanguage ) ) + { + if ( primary( nTmpLocaleLang ) == primary( nReferenceLanguage ) ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = nReferenceLanguage; + } + } + break; + // Special cases + case 0x04 : // Devanagari (Hindi) numerals + // same numerals (Devanagari) for languages with different primary masks + if ( nLocaleLang != LANGUAGE_HINDI && nLocaleLang != LANGUAGE_MARATHI + && primary( nLocaleLang ) != primary( LANGUAGE_NEPALI ) ) + { + if ( nTmpLocaleLang == LANGUAGE_HINDI || nTmpLocaleLang == LANGUAGE_MARATHI + || primary( nTmpLocaleLang ) == primary( LANGUAGE_NEPALI ) ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = LANGUAGE_HINDI; + } + } + break; + case 0x13 : // Mongolian numerals + // not all Mongolian languages use Mongolian numerals + if ( nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA + && nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA + && nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_LSO ) + { + if ( nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA + || nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA + || nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_LSO ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA; + } + } + break; + case 0x02 : // Eastern-Arabic numerals + // all arabic primary mask + LANGUAGE_PUNJABI_ARABIC_LSO + if ( primary( nLocaleLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY + && nLocaleLang != LANGUAGE_PUNJABI_ARABIC_LSO ) + { + if ( primary( nTmpLocaleLang ) == LANGUAGE_ARABIC_PRIMARY_ONLY + || nTmpLocaleLang != LANGUAGE_PUNJABI_ARABIC_LSO ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = nReferenceLanguage; + } + } + break; + // CJK numerals + case 0x1B : // simple Asian numerals, Japanese + case 0x1C : // financial Asian numerals, Japanese + case 0x1D : // Arabic fullwidth numerals, Japanese + case 0x24 : // simple Asian numerals, Korean + case 0x25 : // financial Asian numerals, Korean + case 0x26 : // Arabic fullwidth numerals, Korean + case 0x27 : // Korean Hangul numerals + // Japanese and Korean are regular + if ( primary( nLocaleLang ) != primary( nReferenceLanguage ) ) + { + if ( primary( nTmpLocaleLang ) == primary( nReferenceLanguage ) ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = nReferenceLanguage; + } + } + [[fallthrough]]; + case 0x1E : // simple Asian numerals, Chinese-PRC + case 0x1F : // financial Asian numerals, Chinese-PRC + case 0x20 : // Arabic fullwidth numerals, Chinese-PRC + case 0x21 : // simple Asian numerals, Chinese-Taiwan + case 0x22 : // financial Asian numerals, Chinese-Taiwan + case 0x23 : // Arabic fullwidth numerals, Chinese-Taiwan + nNatNum = nNumeralID == 0x27 ? 9 : ( ( nNumeralID - 0x1B ) % 3 ) + 1; + // [NatNum1] simple numerals + // [natNum2] financial numerals + // [NatNum3] Arabic fullwidth numerals + // Chinese simplified and Chinese traditional have same primary mask + // Chinese-PRC + if ( nReferenceLanguage == LANGUAGE_CHINESE_SIMPLIFIED + && nLocaleLang != LANGUAGE_CHINESE_SIMPLIFIED + && nLocaleLang != LANGUAGE_CHINESE_SINGAPORE + && nLocaleLang != LANGUAGE_CHINESE_LSO ) + { + if ( nTmpLocaleLang == LANGUAGE_CHINESE_SIMPLIFIED + || nTmpLocaleLang == LANGUAGE_CHINESE_SINGAPORE + || nTmpLocaleLang == LANGUAGE_CHINESE_LSO ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = LANGUAGE_CHINESE_SIMPLIFIED; + } + } + // Chinese-Taiwan + else if ( nReferenceLanguage == LANGUAGE_CHINESE_TRADITIONAL + && nLocaleLang != LANGUAGE_CHINESE_TRADITIONAL + && nLocaleLang != LANGUAGE_CHINESE_HONGKONG + && nLocaleLang != LANGUAGE_CHINESE_MACAU ) + { + if ( nTmpLocaleLang == LANGUAGE_CHINESE_TRADITIONAL + || nTmpLocaleLang == LANGUAGE_CHINESE_HONGKONG + || nTmpLocaleLang == LANGUAGE_CHINESE_MACAU ) + { + nLang = maLocale.meLanguage = aTmpLocale.meLanguage; + } + else + { + nLang = maLocale.meLanguage = LANGUAGE_CHINESE_TRADITIONAL; + } + } + break; + } + if ( nNumeralID >= 0x02 && nNumeralID <= 0x13 ) + nNatNum = 1; + if ( nNatNum ) + rString.insert( nPos, OUStringConcatenation("[NatNum"+OUString::number(nNatNum)+"]")); + return sCalendar; +} + +namespace +{ +bool NatNumTakesParameters(sal_Int16 nNum) +{ + return (nNum == css::i18n::NativeNumberMode::NATNUM12); +} +} + +SvNumberformat::SvNumberformat(OUString& rString, + ImpSvNumberformatScan* pSc, + ImpSvNumberInputScan* pISc, + sal_Int32& nCheckPos, + LanguageType& eLan, + bool bReplaceBooleanEquivalent) + : rScan(*pSc) + , bAdditionalBuiltin( false ) + , bStarFlag( false ) +{ + if (bReplaceBooleanEquivalent) + rScan.ReplaceBooleanEquivalent( rString); + + OUStringBuffer sBuff(rString); + + // If the group (AKA thousand) separator is a No-Break Space (French) + // replace all occurrences by a simple space. + // The same for Narrow No-Break Space just in case some locale uses it. + // The tokens will be changed to the LocaleData separator again later on. + const OUString& rThSep = GetFormatter().GetNumThousandSep(); + if ( rThSep.getLength() == 1) + { + const sal_Unicode cNBSp = 0xA0; + const sal_Unicode cNNBSp = 0x202F; + if (rThSep[0] == cNBSp ) + sBuff.replace( cNBSp, ' '); + else if (rThSep[0] == cNNBSp ) + sBuff.replace( cNNBSp, ' '); + } + + OUString aConvertFromDecSep; + OUString aConvertToDecSep; + if (rScan.GetConvertMode()) + { + aConvertFromDecSep = GetFormatter().GetNumDecimalSep(); + maLocale.meLanguage = rScan.GetNewLnge(); + eLan = maLocale.meLanguage; // Make sure to return switch + } + else + { + maLocale.meLanguage = eLan; + } + bStandard = false; + bIsUsed = false; + fLimit1 = 0.0; + fLimit2 = 0.0; + eOp1 = NUMBERFORMAT_OP_NO; + eOp2 = NUMBERFORMAT_OP_NO; + eType = SvNumFormatType::DEFINED; + + bool bCancel = false; + bool bCondition = false; + short eSymbolType; + sal_Int32 nPos = 0; + sal_Int32 nPosOld; + nCheckPos = 0; + + // Split into 4 sub formats + sal_uInt16 nIndex; + for ( nIndex = 0; nIndex < 4 && !bCancel; nIndex++ ) + { + // Original language/country may have to be reestablished + if (rScan.GetConvertMode()) + { + rScan.GetNumberformatter()->ChangeIntl(rScan.GetTmpLnge()); + } + OUString sInsertCalendar; // a calendar resulting from parsing LCID + OUString sStr; + nPosOld = nPos; // Start position of substring + // first get bracketed prefixes; e.g. conditions, color + do + { + eSymbolType = ImpNextSymbol(sBuff, nPos, sStr); + if (eSymbolType > 0) // condition + { + if ( nIndex == 0 && !bCondition ) + { + bCondition = true; + eOp1 = static_cast(eSymbolType); + } + else if ( nIndex == 1 && bCondition ) + { + eOp2 = static_cast(eSymbolType); + } + else // error + { + bCancel = true; // break for + nCheckPos = nPosOld; + } + if (!bCancel) + { + double fNumber; + sal_Int32 nCntChars = ImpGetNumber(sBuff, nPos, sStr); + if (nCntChars > 0) + { + sal_Int32 nDecPos; + SvNumFormatType F_Type = SvNumFormatType::UNDEFINED; + if (!pISc->IsNumberFormat(sStr, F_Type, fNumber, nullptr, SvNumInputOptions::NONE) || + ( F_Type != SvNumFormatType::NUMBER && + F_Type != SvNumFormatType::SCIENTIFIC) ) + { + fNumber = 0.0; + nPos = nPos - nCntChars; + sBuff.remove(nPos, nCntChars); + sBuff.insert(nPos, '0'); + nPos++; + } + else if (rScan.GetConvertMode() && ((nDecPos = sStr.indexOf( aConvertFromDecSep)) >= 0)) + { + if (aConvertToDecSep.isEmpty()) + aConvertToDecSep = GetFormatter().GetLangDecimalSep( rScan.GetNewLnge()); + if (aConvertToDecSep != aConvertFromDecSep) + { + const OUString aStr( sStr.replaceAt( nDecPos, + aConvertFromDecSep.getLength(), aConvertToDecSep)); + nPos = nPos - nCntChars; + sBuff.remove(nPos, nCntChars); + sBuff.insert(nPos, aStr); + nPos += aStr.getLength(); + } + } + } + else + { + fNumber = 0.0; + sBuff.insert(nPos++, '0'); + } + if (nIndex == 0) + { + fLimit1 = fNumber; + } + else + { + fLimit2 = fNumber; + } + if ( nPos < sBuff.getLength() && sBuff[nPos] == ']' ) + { + nPos++; + } + else + { + bCancel = true; // break for + nCheckPos = nPos; + } + } + nPosOld = nPos; // position before string + } + else if ( lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) ) + { + OUString sSymbol( sStr); + switch ( eSymbolType ) + { + case BRACKET_SYMBOLTYPE_COLOR : + if ( NumFor[nIndex].GetColor() != nullptr ) + { // error, more than one color + bCancel = true; // break for + nCheckPos = nPosOld; + } + else + { + const Color* pColor = pSc->GetColor( sStr); + NumFor[nIndex].SetColor( pColor, sStr); + if (pColor == nullptr) + { // error + bCancel = true; // break for + nCheckPos = nPosOld; + } + } + break; + case BRACKET_SYMBOLTYPE_NATNUM0 : + case BRACKET_SYMBOLTYPE_NATNUM1 : + case BRACKET_SYMBOLTYPE_NATNUM2 : + case BRACKET_SYMBOLTYPE_NATNUM3 : + case BRACKET_SYMBOLTYPE_NATNUM4 : + case BRACKET_SYMBOLTYPE_NATNUM5 : + case BRACKET_SYMBOLTYPE_NATNUM6 : + case BRACKET_SYMBOLTYPE_NATNUM7 : + case BRACKET_SYMBOLTYPE_NATNUM8 : + case BRACKET_SYMBOLTYPE_NATNUM9 : + case BRACKET_SYMBOLTYPE_NATNUM10 : + case BRACKET_SYMBOLTYPE_NATNUM11 : + case BRACKET_SYMBOLTYPE_NATNUM12 : + case BRACKET_SYMBOLTYPE_NATNUM13 : + case BRACKET_SYMBOLTYPE_NATNUM14 : + case BRACKET_SYMBOLTYPE_NATNUM15 : + case BRACKET_SYMBOLTYPE_NATNUM16 : + case BRACKET_SYMBOLTYPE_NATNUM17 : + case BRACKET_SYMBOLTYPE_NATNUM18 : + case BRACKET_SYMBOLTYPE_NATNUM19 : + if ( NumFor[nIndex].GetNatNum().IsSet() ) + { + bCancel = true; // break for + nCheckPos = nPosOld; + } + else + { + OUString sParams; + sal_Int32 nSpacePos = sStr.indexOf(' '); + if (nSpacePos >= 0) + { + sParams = o3tl::trim(sStr.subView(nSpacePos+1)); + } + //! eSymbolType is negative + sal_uInt8 nNum = static_cast(0 - (eSymbolType - BRACKET_SYMBOLTYPE_NATNUM0)); + if (!sParams.isEmpty() && !NatNumTakesParameters(nNum)) + { + bCancel = true; // break for + nCheckPos = nPosOld; + break; + } + sStr = "NatNum" + OUString::number(nNum); + NumFor[nIndex].SetNatNumNum( nNum, false ); + // NatNum12 supports arguments + if (nNum == 12) + { + if (sParams.isEmpty()) + sParams = "cardinal"; // default NatNum12 format is "cardinal" + NumFor[nIndex].SetNatNumParams(sParams); + sStr += " " + sParams; + } + } + break; + case BRACKET_SYMBOLTYPE_DBNUM1 : + case BRACKET_SYMBOLTYPE_DBNUM2 : + case BRACKET_SYMBOLTYPE_DBNUM3 : + case BRACKET_SYMBOLTYPE_DBNUM4 : + case BRACKET_SYMBOLTYPE_DBNUM5 : + case BRACKET_SYMBOLTYPE_DBNUM6 : + case BRACKET_SYMBOLTYPE_DBNUM7 : + case BRACKET_SYMBOLTYPE_DBNUM8 : + case BRACKET_SYMBOLTYPE_DBNUM9 : + if ( NumFor[nIndex].GetNatNum().IsSet() ) + { + bCancel = true; // break for + nCheckPos = nPosOld; + } + else + { + //! eSymbolType is negative + sal_uInt8 nNum = static_cast(1 - (eSymbolType - BRACKET_SYMBOLTYPE_DBNUM1)); + sStr = "DBNum" + OUStringChar(sal_Unicode('0' + nNum)); + NumFor[nIndex].SetNatNumNum( nNum, true ); + } + break; + case BRACKET_SYMBOLTYPE_LOCALE : + if ( NumFor[nIndex].GetNatNum().GetLang() != LANGUAGE_DONTKNOW || + sBuff[nPos-1] != ']' ) + // Check also for ']' to avoid pulling in + // locale data for the preview string for not + // yet completed LCIDs in the dialog. + { + bCancel = true; // break for + nCheckPos = nPosOld; + } + else + { + sal_Int32 nTmp = 2; + LocaleType aTmpLocale( ImpGetLocaleType( sStr, nTmp)); + if (aTmpLocale.meLanguage == LANGUAGE_DONTKNOW) + { + bCancel = true; // break for + nCheckPos = nPosOld; + } + else + { + // Only the first sub format's locale will be + // used as the format's overall locale. + // Sorts this also under the corresponding + // locale for the dialog. + // If we don't support the locale this would + // result in an unknown (empty) language + // listbox entry and the user would never see + // this format. + if (nIndex == 0 && (aTmpLocale.meLanguage == LANGUAGE_SYSTEM || + SvNumberFormatter::IsLocaleInstalled( aTmpLocale.meLanguage))) + { + maLocale = aTmpLocale; + eLan = aTmpLocale.meLanguage; // return to caller + + // Set new target locale also at scanner. + // We have to do this because switching locale + // may make replacing keywords and separators + // necessary. + // We can do this because it's the first + // subformat and we're still at parsing the + // modifiers, not keywords. + rScan.SetNewLnge( eLan); + // We can not force conversion though because + // the caller may have explicitly not set it. + // In the usual case the target locale is the + // originating locale the conversion is not + // necessary, when reading alien documents + // conversion is enabled anyway. + + /* TODO: fiddle with scanner to make this + * known? A change in the locale may affect + * separators and keywords. On the other + * hand they may have been entered as used + * in the originating locale, there's no + * way to predict other than analyzing the + * format code, we assume here the current + * context is used, which is most likely + * the case. + * */ + + // Strip a plain locale identifier if locale + // data is available to avoid duplicated + // formats with and without LCID for the same + // locale. Besides it looks ugly and confusing + // and is unnecessary as the format will be + // listed for the resulting locale. + if (aTmpLocale.isPlainLocale()) + sStr.clear(); + else + sStr = "$-" + aTmpLocale.generateCode(); + } + else + { + if (nIndex == 0) + // Locale data not available, remember. + maLocale.meLanguageWithoutLocaleData = aTmpLocale.meLanguage; + + sStr = "$-" + aTmpLocale.generateCode(); + } + NumFor[nIndex].SetNatNumLang( MsLangId::getRealLanguage( aTmpLocale.meLanguage)); + + // "$-NNCCLLLL" Numerals and Calendar + if (sSymbol.getLength() > 6) + { + sInsertCalendar = ImpObtainCalendarAndNumerals( sBuff, nPos, eLan, aTmpLocale); + } + /* NOTE: there can be only one calendar + * inserted so the last one wins, though + * our own calendar modifiers support + * multiple calendars within one sub format + * code if at different positions. */ + } + } + break; + } + if ( !bCancel ) + { + if (sStr == sSymbol) + { + nPosOld = nPos; + } + else + { + sBuff.remove(nPosOld, nPos - nPosOld); + if (!sStr.isEmpty()) + { + sBuff.insert(nPosOld, sStr); + nPos = nPosOld + sStr.getLength(); + sBuff.insert(nPos, "]"); + sBuff.insert(nPosOld, "["); + nPos += 2; + nPosOld = nPos; // position before string + } + else + { + nPos = nPosOld; // prefix removed for whatever reason + } + } + } + } + } + while ( !bCancel && lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) ); + + // The remaining format code string + if ( !bCancel ) + { + if (eSymbolType == BRACKET_SYMBOLTYPE_FORMAT) + { + if (nIndex == 1 && eOp1 == NUMBERFORMAT_OP_NO) + { + eOp1 = NUMBERFORMAT_OP_GT; // undefined condition, default: > 0 + } + else if (nIndex == 2 && eOp2 == NUMBERFORMAT_OP_NO) + { + eOp2 = NUMBERFORMAT_OP_LT; // undefined condition, default: < 0 + } + if (sStr.isEmpty()) + { + // Empty sub format. + NumFor[nIndex].Info().eScannedType = SvNumFormatType::EMPTY; + } + else + { + if (!sInsertCalendar.isEmpty()) + { + sStr = sInsertCalendar + sStr; + } + sal_Int32 nStrPos = pSc->ScanFormat( sStr); + sal_uInt16 nCnt = pSc->GetResultStringsCnt(); + if (nCnt == 0 && nStrPos == 0) // error + { + nStrPos = 1; + } + if (nStrPos == 0) // ok + { + // e.g. Thai T speciality + if (pSc->GetNatNumModifier() && !NumFor[nIndex].GetNatNum().IsSet()) + { + sStr = "[NatNum" + OUString::number( pSc->GetNatNumModifier()) + "]" + sStr; + NumFor[nIndex].SetNatNumNum( pSc->GetNatNumModifier(), false ); + } + // #i53826# #i42727# For the Thai T speciality we need + // to freeze the locale and immunize it against + // conversions during exports, just in case we want to + // save to Xcl. This disables the feature of being able + // to convert a NatNum to another locale. You can't + // have both. + // FIXME: implement a specialized export conversion + // that works on tokens (have to tokenize all first) + // and doesn't use the format string and + // PutandConvertEntry() to LANGUAGE_ENGLISH_US in + // sc/source/filter/excel/xestyle.cxx + // XclExpNumFmtBuffer::WriteFormatRecord(). + LanguageType eLanguage; + if (NumFor[nIndex].GetNatNum().GetNatNum() == 1 && + ((eLanguage = MsLangId::getRealLanguage( eLan)) == LANGUAGE_THAI) && + NumFor[nIndex].GetNatNum().GetLang() == LANGUAGE_DONTKNOW) + { + sStr = "[$-" + OUString::number( sal_uInt16(eLanguage), 16 ).toAsciiUpperCase() + "]" + sStr; + NumFor[nIndex].SetNatNumLang( eLanguage); + } + sBuff.remove(nPosOld, nPos - nPosOld); + sBuff.insert(nPosOld, sStr); + nPos = nPosOld + sStr.getLength(); + if (nPos < sBuff.getLength()) + { + sBuff.insert(nPos, ";"); + nPos++; + } + else if (nIndex > 0) + { + // The last subformat. If it is a trailing text + // format the omitted subformats act like they were + // not specified and "inherited" the first format, + // e.g. 0;@ behaves like 0;-0;0;@ + if (pSc->GetScannedType() == SvNumFormatType::TEXT) + { + // Reset conditions, reverting any set above. + if (nIndex == 1) + eOp1 = NUMBERFORMAT_OP_NO; + else if (nIndex == 2) + eOp2 = NUMBERFORMAT_OP_NO; + nIndex = 3; + } + } + NumFor[nIndex].Enlarge(nCnt); + pSc->CopyInfo(&(NumFor[nIndex].Info()), nCnt); + // type check + if (nIndex == 0) + { + eType = NumFor[nIndex].Info().eScannedType; + } + else if (nIndex == 3) + { // #77026# Everything recognized IS text + NumFor[nIndex].Info().eScannedType = SvNumFormatType::TEXT; + } + else if ( NumFor[nIndex].Info().eScannedType != eType) + { + eType = SvNumFormatType::DEFINED; + } + } + else + { + nCheckPos = nPosOld + nStrPos; // error in string + bCancel = true; // break for + } + } + } + else if (eSymbolType == BRACKET_SYMBOLTYPE_ERROR) // error + { + nCheckPos = nPosOld; + bCancel = true; + } + else if ( lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) ) + { + nCheckPos = nPosOld + 1; // error, prefix in string + bCancel = true; // break for + } + } + if ( bCancel && !nCheckPos ) + { + nCheckPos = 1; // nCheckPos is used as an error condition + } + if ( !bCancel ) + { + if ( NumFor[nIndex].GetNatNum().IsSet() && + NumFor[nIndex].GetNatNum().GetLang() == LANGUAGE_DONTKNOW ) + { + NumFor[nIndex].SetNatNumLang( eLan ); + } + } + if (sBuff.getLength() == nPos) + { + if (nIndex < 3 && rString[rString.getLength()-1] == ';') + { + // A trailing ';' is significant and specifies the following + // subformat to be empty. We don't enter the scanning loop + // above again though. + // Note that the operators apply to the current last scanned + // subformat. + if (nIndex == 0 && eOp1 == NUMBERFORMAT_OP_NO) + { + eOp1 = NUMBERFORMAT_OP_GT; // undefined condition, default: > 0 + } + else if (nIndex == 1 && eOp2 == NUMBERFORMAT_OP_NO) + { + eOp2 = NUMBERFORMAT_OP_LT; // undefined condition, default: < 0 + } + NumFor[nIndex+1].Info().eScannedType = SvNumFormatType::EMPTY; + if (sBuff[nPos-1] != ';') + sBuff.insert( nPos++, ';'); + } + if (nIndex == 2 && eSymbolType == BRACKET_SYMBOLTYPE_FORMAT && sBuff[nPos-1] == ';') + { + // #83510# A 4th subformat explicitly specified to be empty + // hides any text. Need the type here for HasTextFormat() + NumFor[3].Info().eScannedType = SvNumFormatType::TEXT; + } + bCancel = true; + } + if ( NumFor[nIndex].GetNatNum().IsSet() ) + { + NumFor[nIndex].SetNatNumDate( bool(NumFor[nIndex].Info().eScannedType & SvNumFormatType::DATE) ); + } + } + + if (!nCheckPos && IsSubstituted()) + { + // For to be substituted formats the scanned type must match the + // substitute type. + if (IsSystemTimeFormat()) + { + if ((eType & ~SvNumFormatType::DEFINED) != SvNumFormatType::TIME) + nCheckPos = std::max( sBuff.indexOf(']') + 1, 1); + } + else if (IsSystemLongDateFormat()) + { + if ((eType & ~SvNumFormatType::DEFINED) != SvNumFormatType::DATE) + nCheckPos = std::max( sBuff.indexOf(']') + 1, 1); + } + else + assert(!"unhandled substitute"); + } + + if ( bCondition && !nCheckPos ) + { + if ( nIndex == 1 && NumFor[0].GetCount() == 0 && + sBuff[sBuff.getLength() - 1] != ';' ) + { + // No format code => GENERAL but not if specified empty + OUString aAdd( pSc->GetStandardName() ); + if ( !pSc->ScanFormat( aAdd ) ) + { + sal_uInt16 nCnt = pSc->GetResultStringsCnt(); + if ( nCnt ) + { + NumFor[0].Enlarge(nCnt); + pSc->CopyInfo( &(NumFor[0].Info()), nCnt ); + sBuff.append(aAdd); + } + } + } + else if ( nIndex == 1 && NumFor[nIndex].GetCount() == 0 && + sBuff[sBuff.getLength() - 1] != ';' && + (NumFor[0].GetCount() > 1 || + (NumFor[0].GetCount() == 1 && + NumFor[0].Info().nTypeArray[0] != NF_KEY_GENERAL)) ) + { + // No trailing second subformat => GENERAL but not if specified empty + // and not if first subformat is GENERAL + OUString aAdd( pSc->GetStandardName() ); + if ( !pSc->ScanFormat( aAdd ) ) + { + sal_uInt16 nCnt = pSc->GetResultStringsCnt(); + if ( nCnt ) + { + NumFor[nIndex].Enlarge(nCnt); + pSc->CopyInfo( &(NumFor[nIndex].Info()), nCnt ); + sBuff.append(";"); + sBuff.append(aAdd); + } + } + } + else if ( nIndex == 2 && NumFor[nIndex].GetCount() == 0 && + sBuff[sBuff.getLength() - 1] != ';' && + eOp2 != NUMBERFORMAT_OP_NO ) + { + // No trailing third subformat => GENERAL but not if specified empty + OUString aAdd( pSc->GetStandardName() ); + if ( !pSc->ScanFormat( aAdd ) ) + { + sal_uInt16 nCnt = pSc->GetResultStringsCnt(); + if ( nCnt ) + { + NumFor[nIndex].Enlarge(nCnt); + pSc->CopyInfo( &(NumFor[nIndex].Info()), nCnt ); + sBuff.append(";"); + sBuff.append(aAdd); + } + } + } + } + rString = sBuff.makeStringAndClear(); + sFormatstring = rString; + + if (NumFor[2].GetCount() == 0 && // No third partial string + eOp1 == NUMBERFORMAT_OP_GT && eOp2 == NUMBERFORMAT_OP_NO && + fLimit1 == 0.0 && fLimit2 == 0.0) + { + eOp1 = NUMBERFORMAT_OP_GE; // Add 0 to the first format + } + +} + +SvNumberformat::~SvNumberformat() +{ +} + +/** + * Next_Symbol + * + * Splits up the symbols for further processing (by the Turing machine) + * + * Start state = SsStart, * = Special state + * ---------------+-------------------+----------------------------+--------------- + * Old State | Symbol read | Event | New state + * ---------------+-------------------+----------------------------+--------------- + * SsStart | " | Symbol += Character | SsGetQuoted + * | ; | Pos-- | SsGetString + * | [ | Symbol += Character | SsGetBracketed + * | ] | Error | SsStop + * | BLANK | | + * | Else | Symbol += Character | SsGetString + * ---------------+-------------------+----------------------------+--------------- + * SsGetString | " | Symbol += Character | SsGetQuoted + * | ; | | SsStop + * | Else | Symbol += Character | + * ---------------+-------------------+----------------------------+--------------- + * SsGetQuoted | " | Symbol += Character | SsGetString + * | Else | Symbol += Character | + * ---------------+-------------------+----------------------------+--------------- + * SsGetBracketed | <, > = | del [ | + * | | Symbol += Character | SsGetCon + * | BLANK | | + * | h, H, m, M, s, S | Symbol += Character | SsGetTime + * | Else | del [ | + * | | Symbol += Character | SsGetPrefix + * ---------------+-------------------+----------------------------+--------------- + * SsGetTime | ] | Symbol += Character | SsGetString + * | h, H, m, M, s, S | Symbol += Character, * | SsGetString + * | Else | del [; Symbol += Character | SsGetPrefix + * ---------------+-------------------+----------------------------+--------------- + * SsGetPrefix | ] | | SsStop + * | Else | Symbol += Character | + * ---------------+-------------------+----------------------------+--------------- + * SsGetCon | >, = | Symbol += Character | + * | ] | | SsStop + * | Else | Error | SsStop + * ---------------+-------------------+----------------------------+--------------- + */ + +namespace { + +enum ScanState +{ + SsStop, + SsStart, + SsGetCon, // condition + SsGetString, // format string + SsGetPrefix, // color or NatNumN + SsGetTime, // [HH] for time + SsGetBracketed, // any [...] not decided yet + SsGetQuoted // quoted text +}; + +} + +// read a string until ']' and delete spaces in input +// static +sal_Int32 SvNumberformat::ImpGetNumber(OUStringBuffer& rString, + sal_Int32& nPos, + OUString& sSymbol) +{ + sal_Int32 nStartPos = nPos; + sal_Unicode cToken; + sal_Int32 nLen = rString.getLength(); + OUStringBuffer sBuffSymbol; + while ( nPos < nLen ) + { + cToken = rString[nPos]; + if (cToken == ']') + break; + if (cToken == ' ') + { // delete spaces + rString.remove(nPos,1); + nLen--; + } + else + { + nPos++; + sBuffSymbol.append(cToken); + } + } + sSymbol = sBuffSymbol.makeStringAndClear(); + return nPos - nStartPos; +} + +namespace { + +sal_Unicode toUniChar(sal_uInt8 n) +{ + char c; + if (n < 10) + { + c = '0' + n; + } + else + { + c = 'A' + n - 10; + } + return sal_Unicode(c); +} + +bool IsCombiningSymbol( OUStringBuffer& rStringBuffer, sal_Int32 nPos ) +{ + bool bRet = false; + while (nPos >= 0) + { + switch (rStringBuffer[nPos]) + { + case '*': + case '\\': + case '_': + bRet = !bRet; + --nPos; + break; + default: + return bRet; + } + } + return bRet; +} + +} // namespace + +OUString SvNumberformat::LocaleType::generateCode() const +{ + OUStringBuffer aBuf; +#if 0 + // TODO: We may re-enable this later. Don't remove it! --Kohei + if (mnNumeralShape) + { + sal_uInt8 nVal = mnNumeralShape; + for (sal_uInt8 i = 0; i < 2; ++i) + { + sal_uInt8 n = (nVal & 0xF0) >> 4; + if (n || aBuf.getLength()) + { + aBuf.append(toUniChar(n)); + } + nVal = nVal << 4; + } + } + + if (mnNumeralShape || mnCalendarType) + { + sal_uInt8 nVal = mnCalendarType; + for (sal_uInt8 i = 0; i < 2; ++i) + { + sal_uInt8 n = (nVal & 0xF0) >> 4; + if (n || aBuf.getLength()) + { + aBuf.append(toUniChar(n)); + } + nVal = nVal << 4; + } + } +#endif + + sal_uInt16 n16 = static_cast( + (meLanguageWithoutLocaleData == LANGUAGE_DONTKNOW) ? meLanguage : + meLanguageWithoutLocaleData); + if (meLanguage == LANGUAGE_SYSTEM) + { + switch (meSubstitute) + { + case Substitute::NONE: + ; // nothing + break; + case Substitute::TIME: + n16 = static_cast(LANGUAGE_NF_SYSTEM_TIME); + break; + case Substitute::LONGDATE: + n16 = static_cast(LANGUAGE_NF_SYSTEM_DATE); + break; + } + } + for (sal_uInt8 i = 0; i < 4; ++i) + { + sal_uInt8 n = static_cast((n16 & 0xF000) >> 12); + // Omit leading zeros for consistency. + if (n || !aBuf.isEmpty() || i == 3) + { + aBuf.append(toUniChar(n)); + } + n16 = n16 << 4; + } + + return aBuf.makeStringAndClear(); +} + +SvNumberformat::LocaleType::LocaleType() + : meLanguage(LANGUAGE_DONTKNOW) + , meLanguageWithoutLocaleData(LANGUAGE_DONTKNOW) + , meSubstitute(Substitute::NONE) + , mnNumeralShape(0) + , mnCalendarType(0) +{ +} + +SvNumberformat::LocaleType::LocaleType(sal_uInt32 nRawNum) + : meLanguage(LANGUAGE_DONTKNOW) + , meLanguageWithoutLocaleData(LANGUAGE_DONTKNOW) + , meSubstitute(Substitute::NONE) + , mnNumeralShape(0) + , mnCalendarType(0) +{ + meLanguage = static_cast(nRawNum & 0x0000FFFF); + if (meLanguage == LANGUAGE_NF_SYSTEM_TIME) + { + meSubstitute = Substitute::TIME; + meLanguage = LANGUAGE_SYSTEM; + } + else if (meLanguage == LANGUAGE_NF_SYSTEM_DATE) + { + meSubstitute = Substitute::LONGDATE; + meLanguage = LANGUAGE_SYSTEM; + } + nRawNum = (nRawNum >> 16); + mnCalendarType = static_cast(nRawNum & 0xFF); + nRawNum = (nRawNum >> 8); + mnNumeralShape = static_cast(nRawNum & 0xFF); +} + +bool SvNumberformat::LocaleType::isPlainLocale() const +{ + return meSubstitute == Substitute::NONE && !mnCalendarType && !mnNumeralShape; +} + +// static +SvNumberformat::LocaleType SvNumberformat::ImpGetLocaleType(const OUString& rString, sal_Int32& nPos ) +{ + sal_uInt32 nNum = 0; + sal_Unicode cToken = 0; + sal_Int32 nStart = nPos; + sal_Int32 nLen = rString.getLength(); + while ( nPos < nLen && (nPos - nStart < 8) ) + { + cToken = rString[nPos]; + if (cToken == ']') + break; + if ( '0' <= cToken && cToken <= '9' ) + { + nNum *= 16; + nNum += cToken - '0'; + } + else if ( 'a' <= cToken && cToken <= 'f' ) + { + nNum *= 16; + nNum += cToken - 'a' + 10; + } + else if ( 'A' <= cToken && cToken <= 'F' ) + { + nNum *= 16; + nNum += cToken - 'A' + 10; + } + else + { + return LocaleType(); // LANGUAGE_DONTKNOW; + } + ++nPos; + } + + return (cToken == ']' || nPos == nLen) ? LocaleType(nNum) : LocaleType(); +} + +static bool lcl_matchKeywordAndGetNumber( const OUString & rString, const sal_Int32 nPos, + const OUString & rKeyword, sal_Int32 & nNumber ) +{ + if (0 <= nPos && nPos + rKeyword.getLength() < rString.getLength() && rString.matchIgnoreAsciiCase( rKeyword, nPos)) + { + nNumber = o3tl::toInt32(rString.subView( nPos + rKeyword.getLength())); + return true; + } + else + { + nNumber = 0; + return false; + } +} + +short SvNumberformat::ImpNextSymbol(OUStringBuffer& rString, + sal_Int32& nPos, + OUString& sSymbol) const +{ + short eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; + sal_Unicode cToken; + sal_Unicode cLetter = ' '; // Preliminary result + sal_Int32 nLen = rString.getLength(); + ScanState eState = SsStart; + OUStringBuffer sBuffSymbol(128); + + const NfKeywordTable & rKeywords = rScan.GetKeywords(); + while (nPos < nLen && eState != SsStop) + { + cToken = rString[nPos]; + nPos++; + switch (eState) + { + case SsStart: + if (cToken == '\"') + { + eState = SsGetQuoted; + sBuffSymbol.append(cToken); + } + else if (cToken == '[') + { + eState = SsGetBracketed; + sBuffSymbol.append(cToken); + } + else if (cToken == ';') + { + eState = SsGetString; + nPos--; + eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; + } + else if (cToken == ']') + { + eState = SsStop; + eSymbolType = BRACKET_SYMBOLTYPE_ERROR; + } + else if (cToken == ' ') // Skip Blanks + { + nPos--; + rString.remove(nPos, 1); + nLen--; + } + else + { + sBuffSymbol.append(cToken); + eState = SsGetString; + eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; + } + break; + case SsGetBracketed: + switch (cToken) + { + case '<': + case '>': + case '=': + sBuffSymbol.stripStart('['); + sBuffSymbol.append(cToken); + cLetter = cToken; + eState = SsGetCon; + switch (cToken) + { + case '<': + eSymbolType = NUMBERFORMAT_OP_LT; + break; + case '>': + eSymbolType = NUMBERFORMAT_OP_GT; + break; + case '=': + eSymbolType = NUMBERFORMAT_OP_EQ; + break; + } + break; + case ' ': + nPos--; + rString.remove(nPos, 1); + nLen--; + break; + case '$' : + if ( nPos < nLen && rString[nPos] == '-' ) + { + // [$-xxx] locale + sBuffSymbol.stripStart('['); + eSymbolType = BRACKET_SYMBOLTYPE_LOCALE; + eState = SsGetPrefix; + } + else + { // currency + eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; + eState = SsGetString; + } + sBuffSymbol.append(cToken); + break; + case '~' : + // calendarID + eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; + sBuffSymbol.append(cToken); + eState = SsGetString; + break; + default: + { + static const OUStringLiteral aNatNum(u"NATNUM"); + static const OUStringLiteral aDBNum(u"DBNUM"); + const OUString aBufStr( rString.toString()); + sal_Int32 nNatNumNum; + sal_Int32 nDBNum; + if ( lcl_matchKeywordAndGetNumber( aBufStr, nPos-1, aNatNum, nNatNumNum) && + 0 <= nNatNumNum && nNatNumNum <= 19 ) + { + sBuffSymbol.stripStart('['); + sBuffSymbol.append( aBufStr.subView(--nPos, aNatNum.getLength()+1) ); + nPos += aNatNum.getLength()+1; + //! SymbolType is negative + eSymbolType = static_cast(BRACKET_SYMBOLTYPE_NATNUM0 - nNatNumNum); + eState = SsGetPrefix; + } + else if ( lcl_matchKeywordAndGetNumber( aBufStr, nPos-1, aDBNum, nDBNum) && + 1 <= nDBNum && nDBNum <= 9 ) + { + sBuffSymbol.stripStart('['); + sBuffSymbol.append( aBufStr.subView(--nPos, aDBNum.getLength()+1) ); + nPos += aDBNum.getLength()+1; + //! SymbolType is negative + eSymbolType = sal::static_int_cast< short >( BRACKET_SYMBOLTYPE_DBNUM1 - (nDBNum - 1) ); + eState = SsGetPrefix; + } + else + { + sal_Unicode cUpper = rChrCls().uppercase( aBufStr, nPos-1, 1)[0]; + if ( cUpper == rKeywords[NF_KEY_H][0] || // H + cUpper == rKeywords[NF_KEY_MI][0] || // M + cUpper == rKeywords[NF_KEY_S][0] ) // S + { + sBuffSymbol.append(cToken); + eState = SsGetTime; + cLetter = cToken; + } + else + { + sBuffSymbol.stripStart('['); + sBuffSymbol.append(cToken); + eSymbolType = BRACKET_SYMBOLTYPE_COLOR; + eState = SsGetPrefix; + } + } + } + } + break; + case SsGetString: + if (cToken == '\"') + { + eState = SsGetQuoted; + sBuffSymbol.append(cToken); + } + else if (cToken == ';' && (nPos < 2 || !IsCombiningSymbol( rString, nPos-2))) + { + eState = SsStop; + } + else + { + sBuffSymbol.append(cToken); + } + break; + case SsGetQuoted: + if (cToken == '\"') + { + eState = SsGetString; + sBuffSymbol.append(cToken); + } + else + { + sBuffSymbol.append(cToken); + } + break; + case SsGetTime: + if (cToken == ']') + { + sBuffSymbol.append(cToken); + eState = SsGetString; + eSymbolType = BRACKET_SYMBOLTYPE_FORMAT; + } + else + { + sal_Unicode cUpper = rChrCls().uppercase(rString.toString(), nPos-1, 1)[0]; + if (cUpper == rKeywords[NF_KEY_H][0] || // H + cUpper == rKeywords[NF_KEY_MI][0] || // M + cUpper == rKeywords[NF_KEY_S][0] ) // S + { + if (cLetter == cToken) + { + sBuffSymbol.append(cToken); + cLetter = ' '; + } + else + { + sBuffSymbol.stripStart('['); + sBuffSymbol.append(cToken); + eState = SsGetPrefix; + } + } + else + { + sBuffSymbol.stripStart('['); + sBuffSymbol.append(cToken); + eSymbolType = BRACKET_SYMBOLTYPE_COLOR; + eState = SsGetPrefix; + } + } + break; + case SsGetCon: + switch (cToken) + { + case '<': + eState = SsStop; + eSymbolType = BRACKET_SYMBOLTYPE_ERROR; + break; + case '>': + if (cLetter == '<') + { + sBuffSymbol.append(cToken); + cLetter = ' '; + eState = SsStop; + eSymbolType = NUMBERFORMAT_OP_NE; + } + else + { + eState = SsStop; + eSymbolType = BRACKET_SYMBOLTYPE_ERROR; + } + break; + case '=': + if (cLetter == '<') + { + sBuffSymbol.append(cToken); + cLetter = ' '; + eSymbolType = NUMBERFORMAT_OP_LE; + } + else if (cLetter == '>') + { + sBuffSymbol.append(cToken); + cLetter = ' '; + eSymbolType = NUMBERFORMAT_OP_GE; + } + else + { + eState = SsStop; + eSymbolType = BRACKET_SYMBOLTYPE_ERROR; + } + break; + case ' ': + nPos--; + rString.remove(nPos,1); + nLen--; + break; + default: + eState = SsStop; + nPos--; + break; + } + break; + case SsGetPrefix: + if (cToken == ']') + { + eState = SsStop; + } + else + { + sBuffSymbol.append(cToken); + } + break; + default: + break; + } // of switch + } // of while + sSymbol = sBuffSymbol.makeStringAndClear(); + return eSymbolType; +} + +void SvNumberformat::ConvertLanguage( SvNumberFormatter& rConverter, + LanguageType eConvertFrom, + LanguageType eConvertTo ) +{ + sal_Int32 nCheckPos; + sal_uInt32 nKey; + SvNumFormatType nType = eType; + OUString aFormatString( sFormatstring ); + rConverter.PutandConvertEntry( aFormatString, nCheckPos, nType, + nKey, eConvertFrom, eConvertTo, false); + const SvNumberformat* pFormat = rConverter.GetEntry( nKey ); + DBG_ASSERT( pFormat, "SvNumberformat::ConvertLanguage: Conversion without format" ); + if ( pFormat ) + { + ImpCopyNumberformat( *pFormat ); + // Reset values taken over from Formatter/Scanner + // pColor still points to table in temporary Formatter/Scanner + for (ImpSvNumFor & rFormatter : NumFor) + { + OUString aColorName = rFormatter.GetColorName(); + const Color* pColor = rScan.GetColor( aColorName ); + rFormatter.SetColor( pColor, aColorName ); + } + } +} + +bool SvNumberformat::HasNewCurrency() const +{ + for (const auto & j : NumFor) + { + if ( j.HasNewCurrency() ) + { + return true; + } + } + return false; +} + +bool SvNumberformat::GetNewCurrencySymbol( OUString& rSymbol, + OUString& rExtension ) const +{ + for (const auto & j : NumFor) + { + if ( j.GetNewCurrencySymbol( rSymbol, rExtension ) ) + { + return true; + } + } + rSymbol.clear(); + rExtension.clear(); + return false; +} + +// static +OUString SvNumberformat::StripNewCurrencyDelimiters( const OUString& rStr ) +{ + OUStringBuffer aTmp(rStr.getLength()); + sal_Int32 nStartPos, nPos, nLen; + nLen = rStr.getLength(); + nStartPos = 0; + while ( (nPos = rStr.indexOf( "[$", nStartPos )) >= 0 ) + { + sal_Int32 nEnd; + if ( (nEnd = GetQuoteEnd( rStr, nPos )) >= 0 ) + { + aTmp.append(rStr.subView( nStartPos, ++nEnd - nStartPos )); + nStartPos = nEnd; + } + else + { + aTmp.append(rStr.subView(nStartPos, nPos - nStartPos) ); + nStartPos = nPos + 2; + sal_Int32 nDash; + nEnd = nStartPos - 1; + do + { + nDash = rStr.indexOf( '-', ++nEnd ); + nEnd = GetQuoteEnd( rStr, nDash ); + } + while ( nEnd >= 0 ); + sal_Int32 nClose; + nEnd = nStartPos - 1; + do + { + nClose = rStr.indexOf( ']', ++nEnd ); + nEnd = GetQuoteEnd( rStr, nClose ); + } + while ( nEnd >= 0 ); + + if(nClose < 0) + { + /* there should always be a closing ] + * but the old String class would have hidden + * that. so be conservative too + */ + nClose = nLen; + } + + nPos = nClose; + if(nDash >= 0 && nDash < nClose) + { + nPos = nDash; + } + aTmp.append(rStr.subView(nStartPos, nPos - nStartPos) ); + nStartPos = nClose + 1; + } + } + if ( nLen > nStartPos ) + { + aTmp.append(rStr.subView(nStartPos, nLen - nStartPos) ); + } + return aTmp.makeStringAndClear(); +} + +void SvNumberformat::ImpGetOutputStandard(double& fNumber, OUStringBuffer& rOutString) const +{ + OUString sTemp; + ImpGetOutputStandard(fNumber, sTemp); + rOutString = sTemp; +} + +void SvNumberformat::ImpGetOutputStandard(double& fNumber, OUString& rOutString) const +{ + sal_uInt16 nStandardPrec = rScan.GetStandardPrec(); + + if ( fabs(fNumber) > EXP_ABS_UPPER_BOUND ) + { + nStandardPrec = ::std::min(nStandardPrec, static_cast(14)); // limits to 14 decimals + rOutString = ::rtl::math::doubleToUString( fNumber, + rtl_math_StringFormat_E2, nStandardPrec /*2*/, + GetFormatter().GetNumDecimalSep()[0]); + } + else + { + ImpGetOutputStdToPrecision(fNumber, rOutString, nStandardPrec); + } +} + +namespace +{ + +template +bool checkForAll0s(const T& rString, sal_Int32 nIdx=0) +{ + if (nIdx>=rString.getLength()) + return false; + + do + { + if (rString[nIdx]!='0') + return false; + } + while (++nIdx(fNumber == fLimit); + case NUMBERFORMAT_OP_NE: + return static_cast(fNumber != fLimit); + case NUMBERFORMAT_OP_LT: + return static_cast(fNumber < fLimit); + case NUMBERFORMAT_OP_LE: + return static_cast(fNumber <= fLimit); + case NUMBERFORMAT_OP_GT: + return static_cast(fNumber > fLimit); + case NUMBERFORMAT_OP_GE: + return static_cast(fNumber >= fLimit); + default: + return -1; + } +} + +static bool lcl_appendStarFillChar( OUStringBuffer& rBuf, const OUString& rStr ) +{ + // Right during user input the star symbol is the very + // last character before the user enters another one. + if (rStr.getLength() > 1) + { + rBuf.append(u'\x001B'); + rBuf.append(rStr[1]); + return true; + } + return false; +} + +static bool lcl_insertStarFillChar( OUStringBuffer& rBuf, sal_Int32 nPos, const OUString& rStr ) +{ + if (rStr.getLength() > 1) + { + rBuf.insert( nPos, rStr[1]); + rBuf.insert( nPos, u'\x001B'); + return true; + } + return false; +} + +void SvNumberformat::GetOutputString(std::u16string_view sString, + OUString& OutString, + const Color** ppColor) +{ + OUStringBuffer sOutBuff; + sal_uInt16 nIx; + if (eType & SvNumFormatType::TEXT) + { + nIx = 0; + } + else if (NumFor[3].GetCount() > 0) + { + nIx = 3; + } + else + { + *ppColor = nullptr; // no change of color + return; + } + *ppColor = NumFor[nIx].GetColor(); + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + if (rInfo.eScannedType == SvNumFormatType::TEXT) + { + const sal_uInt16 nCnt = NumFor[nIx].GetCount(); + for (sal_uInt16 i = 0; i < nCnt; i++) + { + switch (rInfo.nTypeArray[i]) + { + case NF_SYMBOLTYPE_STAR: + if( bStarFlag ) + { + lcl_appendStarFillChar( sOutBuff, rInfo.sStrArray[i]); + } + break; + case NF_SYMBOLTYPE_BLANK: + if (rInfo.sStrArray[i].getLength() >= 2) + InsertBlanks( sOutBuff, sOutBuff.getLength(), rInfo.sStrArray[i][1] ); + break; + case NF_KEY_GENERAL : // #77026# "General" is the same as "@" + case NF_SYMBOLTYPE_DEL : + sOutBuff.append(sString); + break; + default: + sOutBuff.append(rInfo.sStrArray[i]); + } + } + } + OutString = sOutBuff.makeStringAndClear(); +} + +namespace { + +void lcl_GetOutputStringScientific(double fNumber, sal_uInt16 nCharCount, + const SvNumberFormatter& rFormatter, OUString& rOutString) +{ + bool bSign = std::signbit(fNumber); + + // 1.000E+015 (one digit and the decimal point, and the two chars + + // nExpDigit for the exponential part, totalling 6 or 7). + double fExp = log10( fabs(fNumber) ); + if( fExp < 0.0 ) + fExp = 1.0 - fExp; + sal_uInt16 nCharFormat = 6 + (fExp >= 100.0 ? 1 : 0); + sal_uInt16 nPrec = nCharCount > nCharFormat ? nCharCount - nCharFormat : 0; + if (nPrec && bSign) + { + // Make room for the negative sign. + --nPrec; + } + nPrec = ::std::min(nPrec, static_cast(14)); // limit to 14 decimals. + + rOutString = ::rtl::math::doubleToUString(fNumber, rtl_math_StringFormat_E2, + nPrec, rFormatter.GetNumDecimalSep()[0], true ); +} + +OUString lcl_GetPercentString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt) +{ + sal_Int32 i; + OUStringBuffer aPercentString; + for( i = 0; i < nCnt; i++ ) + { + if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_PERCENT ) + { + aPercentString.append( rInfo.sStrArray[i] ); + bool bStringFound = false; + for( i--; i >= 0 && rInfo.nTypeArray[i] == NF_SYMBOLTYPE_STRING ; i-- ) + { + if( !bStringFound ) + { + bStringFound = true; + aPercentString.insert( 0, "\"" ); + } + aPercentString.insert( 0, rInfo.sStrArray[i] ); + } + i = nCnt; + if( bStringFound ) + aPercentString.insert( 0, "\"" ); + } + } + return aPercentString.makeStringAndClear(); +} + +OUString lcl_GetDenominatorString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt) +{ + sal_Int32 i; + OUStringBuffer aDenominatorString; + for( i = 0; i < nCnt; i++ ) + { + if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC ) + { + while ( ( ++i < nCnt ) && rInfo.nTypeArray[i] != NF_SYMBOLTYPE_FRAC_FDIV + && rInfo.nTypeArray[i] != NF_SYMBOLTYPE_DIGIT ); + for( ; i < nCnt; i++ ) + { + if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC_FDIV || rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT ) + aDenominatorString.append( rInfo.sStrArray[i] ); + else + i = nCnt; + } + } + } + return aDenominatorString.makeStringAndClear(); +} + +OUString lcl_GetNumeratorString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt) +{ + sal_Int32 i; + OUStringBuffer aNumeratorString; + for( i = 0; i < nCnt; i++ ) + { + if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC ) + { + for( i--; i >= 0 && rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT ; i-- ) + { + aNumeratorString.insert( 0, rInfo.sStrArray[i] ); + } + i = nCnt; + } + } + return aNumeratorString.makeStringAndClear(); +} + +OUString lcl_GetFractionIntegerString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt) +{ + sal_Int32 i; + OUStringBuffer aIntegerString; + for( i = 0; i < nCnt; i++ ) + { + if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRACBLANK ) + { + for( i--; i >= 0 && ( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT + || rInfo.nTypeArray[i] == NF_SYMBOLTYPE_THSEP ); i-- ) + { + aIntegerString.insert( 0, rInfo.sStrArray[i] ); + } + i = nCnt; + } + } + return aIntegerString.makeStringAndClear(); +} + +OUString lcl_GetIntegerFractionDelimiterString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt) +{ + sal_uInt16 i; + for( i = 0; i < nCnt; i++ ) + { + if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRACBLANK ) + { + return rInfo.sStrArray[i]; + } + } + return OUString(); +} + +} + +OUString SvNumberformat::GetPercentString( sal_uInt16 nNumFor ) const +{ + const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info(); + sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); + return lcl_GetPercentString( rInfo, nCnt ); +} + +OUString SvNumberformat::GetDenominatorString( sal_uInt16 nNumFor ) const +{ + const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info(); + sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); + return lcl_GetDenominatorString( rInfo, nCnt ); +} + +OUString SvNumberformat::GetNumeratorString( sal_uInt16 nNumFor ) const +{ + const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info(); + sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); + return lcl_GetNumeratorString( rInfo, nCnt ); +} + +OUString SvNumberformat::GetIntegerFractionDelimiterString( sal_uInt16 nNumFor ) const +{ + const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info(); + sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); + return lcl_GetIntegerFractionDelimiterString( rInfo, nCnt ); +} + +bool SvNumberformat::GetOutputString(double fNumber, sal_uInt16 nCharCount, OUString& rOutString) const +{ + if (eType != SvNumFormatType::NUMBER) + { + return false; + } + double fTestNum = fNumber; + bool bSign = std::signbit(fTestNum); + if (bSign) + { + fTestNum = -fTestNum; + } + if (fTestNum < EXP_LOWER_BOUND) + { + lcl_GetOutputStringScientific(fNumber, nCharCount, GetFormatter(), rOutString); + return true; + } + + double fExp = log10(fTestNum); + // Values < 1.0 always have one digit before the decimal point. + sal_uInt16 nDigitPre = fExp >= 0.0 ? static_cast(ceil(fExp)) : 1; + + if (nDigitPre > 15) + { + lcl_GetOutputStringScientific(fNumber, nCharCount, GetFormatter(), rOutString); + return true; + } + + sal_uInt16 nPrec = nCharCount >= nDigitPre ? nCharCount - nDigitPre : 0; + if (nPrec && bSign) + { + // Subtract the negative sign. + --nPrec; + } + if (nPrec) + { + // Subtract the decimal point. + --nPrec; + } + ImpGetOutputStdToPrecision(fNumber, rOutString, nPrec); + if (rOutString.getLength() > nCharCount) + { + // String still wider than desired. Switch to scientific notation. + lcl_GetOutputStringScientific(fNumber, nCharCount, GetFormatter(), rOutString); + } + return true; +} + +sal_uInt16 SvNumberformat::GetSubformatIndex (double fNumber ) const +{ + sal_uInt16 nIx; // Index of the partial format + double fLimit_1 = fLimit1; + short nCheck = ImpCheckCondition(fNumber, fLimit_1, eOp1); + if (nCheck == -1 || nCheck == 1) // Only 1 String or True + { + nIx = 0; + } + else + { + double fLimit_2 = fLimit2; + nCheck = ImpCheckCondition(fNumber, fLimit_2, eOp2); + if (nCheck == -1 || nCheck == 1) + { + nIx = 1; + } + else + { + nIx = 2; + } + } + return nIx; +} + +bool SvNumberformat::GetOutputString(double fNumber, + OUString& OutString, + const Color** ppColor) +{ + bool bRes = false; + OutString.clear(); + *ppColor = nullptr; // No color change + if (eType & SvNumFormatType::LOGICAL && sFormatstring == rScan.GetKeywords()[NF_KEY_BOOLEAN]) + { + if (fNumber) + { + OutString = rScan.GetTrueString(); + } + else + { + OutString = rScan.GetFalseString(); + } + return false; + } + OUStringBuffer sBuff(64); + if (eType & SvNumFormatType::TEXT) + { + ImpGetOutputStandard(fNumber, sBuff); + OutString = sBuff.makeStringAndClear(); + return false; + } + bool bHadStandard = false; + if (bStandard) // Individual standard formats + { + if (rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION) // All number format InputLine + { + ImpGetOutputInputLine(fNumber, OutString); + return false; + } + switch (eType) + { + case SvNumFormatType::NUMBER: // Standard number format + if (rScan.GetStandardPrec() == SvNumberFormatter::UNLIMITED_PRECISION) + { + if (std::signbit(fNumber)) + { + if (!(fNumber < 0.0)) + fNumber = -fNumber; // do not display -0.0 + } + if (fNumber == 0.0) + { + OutString = "0"; + } + else if (fNumber < 1.0 && fNumber > -1.0) + { + // Decide whether to display as 0.000000123... or 1.23...e-07 + bool bFix = (fNumber < -EXP_LOWER_BOUND || EXP_LOWER_BOUND < fNumber); + if (!bFix) + { + // Arbitrary, not too many 0s visually, start E2 at 1E-10. + constexpr sal_Int32 kMaxExp = 9; + const sal_Int32 nExp = static_cast(ceil( -log10( fabs( fNumber)))); + if (nExp <= kMaxExp && rtl::math::approxEqual( + rtl::math::round( fNumber, 16), rtl::math::round( fNumber, nExp + 16))) + { + // Not too many significant digits or accuracy + // artefacts, otherwise leave everything to E2 + // format. + bFix = true; + } + } + if (bFix) + OutString = ::rtl::math::doubleToUString( fNumber, + rtl_math_StringFormat_F, + rtl_math_DecimalPlaces_Max, + GetFormatter().GetNumDecimalSep()[0], true); + else + OutString = ::rtl::math::doubleToUString( fNumber, + rtl_math_StringFormat_E2, + rtl_math_DecimalPlaces_Max, + GetFormatter().GetNumDecimalSep()[0], true); + } + else + { + OutString = ::rtl::math::doubleToUString( fNumber, + rtl_math_StringFormat_Automatic, + rtl_math_DecimalPlaces_Max, + GetFormatter().GetNumDecimalSep()[0], true); + } + return false; + } + ImpGetOutputStandard(fNumber, sBuff); + bHadStandard = true; + break; + case SvNumFormatType::DATE: + bRes |= ImpGetDateOutput(fNumber, 0, sBuff); + bHadStandard = true; + break; + case SvNumFormatType::TIME: + bRes |= ImpGetTimeOutput(fNumber, 0, sBuff); + bHadStandard = true; + break; + case SvNumFormatType::DATETIME: + bRes |= ImpGetDateTimeOutput(fNumber, 0, sBuff); + bHadStandard = true; + break; + default: break; + } + } + if ( !bHadStandard ) + { + sal_uInt16 nIx = GetSubformatIndex ( fNumber ); // Index of the partial format + if (fNumber < 0.0 && + ((nIx == 0 && IsFirstSubformatRealNegative()) || // 1st, usually positive subformat + (nIx == 1 && IsSecondSubformatRealNegative()))) // 2nd, usually negative subformat + { + fNumber = -fNumber; // eliminate sign + } + *ppColor = NumFor[nIx].GetColor(); + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + const sal_uInt16 nCnt = NumFor[nIx].GetCount(); + if (nCnt == 0 && rInfo.eScannedType == SvNumFormatType::EMPTY) + { + return false; // Empty => nothing + } + else if (nCnt == 0) // Else Standard Format + { + ImpGetOutputStandard(fNumber, sBuff); + OutString = sBuff.makeStringAndClear(); + return false; + } + switch (rInfo.eScannedType) + { + case SvNumFormatType::TEXT: + case SvNumFormatType::DEFINED: + for (sal_uInt16 i = 0; i < nCnt; i++) + { + switch (rInfo.nTypeArray[i]) + { + case NF_SYMBOLTYPE_STAR: + if( bStarFlag ) + { + bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]); + } + break; + case NF_SYMBOLTYPE_BLANK: + if (rInfo.sStrArray[i].getLength() >= 2) + InsertBlanks(sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] ); + break; + case NF_SYMBOLTYPE_STRING: + case NF_SYMBOLTYPE_CURRENCY: + sBuff.append(rInfo.sStrArray[i]); + break; + case NF_SYMBOLTYPE_THSEP: + if (rInfo.nThousand == 0) + { + sBuff.append(rInfo.sStrArray[i]); + } + break; + default: + break; + } + } + break; + case SvNumFormatType::DATE: + bRes |= ImpGetDateOutput(fNumber, nIx, sBuff); + break; + case SvNumFormatType::TIME: + bRes |= ImpGetTimeOutput(fNumber, nIx, sBuff); + break; + case SvNumFormatType::DATETIME: + bRes |= ImpGetDateTimeOutput(fNumber, nIx, sBuff); + break; + case SvNumFormatType::NUMBER: + case SvNumFormatType::PERCENT: + case SvNumFormatType::CURRENCY: + bRes |= ImpGetNumberOutput(fNumber, nIx, sBuff); + break; + case SvNumFormatType::LOGICAL: + bRes |= ImpGetLogicalOutput(fNumber, nIx, sBuff); + break; + case SvNumFormatType::FRACTION: + bRes |= ImpGetFractionOutput(fNumber, nIx, sBuff); + break; + case SvNumFormatType::SCIENTIFIC: + bRes |= ImpGetScientificOutput(fNumber, nIx, sBuff); + break; + default: break; + } + } + OutString = sBuff.makeStringAndClear(); + return bRes; +} + +bool SvNumberformat::ImpGetScientificOutput(double fNumber, + sal_uInt16 nIx, + OUStringBuffer& sStr) +{ + bool bRes = false; + bool bSign = false; + + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + const sal_uInt16 nCnt = NumFor[nIx].GetCount(); + + if (fNumber < 0) + { + if (nIx == 0) // Not in the ones at the end + { + bSign = true; // Formats + } + fNumber = -fNumber; + } + + sStr = ::rtl::math::doubleToUString( fNumber, + rtl_math_StringFormat_E, + rInfo.nCntPre + rInfo.nCntPost - 1, '.' ); + OUStringBuffer ExpStr; + short nExpSign = 1; + sal_Int32 nExPos = sStr.indexOf('E'); + sal_Int32 nDecPos = -1; + + if ( nExPos >= 0 ) + { + // split into mantissa and exponent and get rid of "E+" or "E-" + sal_Int32 nExpStart = nExPos + 1; + + switch ( sStr[ nExpStart ] ) + { + case '-' : + nExpSign = -1; + [[fallthrough]]; + case '+' : + ++nExpStart; + break; + } + ExpStr = sStr.subView( nExpStart ); // part following the "E+" + sStr.truncate( nExPos ); + + if ( rInfo.nCntPre != 1 ) // rescale Exp + { + sal_Int32 nExp = OUString::unacquired(ExpStr).toInt32() * nExpSign; + sal_Int32 nRescale = (rInfo.nCntPre != 0) ? nExp % static_cast(rInfo.nCntPre) : -1; + if( nRescale < 0 && rInfo.nCntPre != 0 ) + nRescale += static_cast(rInfo.nCntPre); + nExp -= nRescale; + if ( nExp < 0 ) + { + nExpSign = -1; + nExp = -nExp; + } + else + { + nExpSign = 1; + } + ExpStr = OUString::number( nExp ); + const sal_Unicode cFirstDigit = sStr[0]; + // rescale mantissa + sStr = ::rtl::math::doubleToUString( fNumber, + rtl_math_StringFormat_E, + nRescale + rInfo.nCntPost, '.' ); + + // sStr now may contain a rounded-up value shifted into the next + // magnitude, for example 1.000E+02 (4 digits) for fNumber 99.995 + // (9.9995E+02 rounded to 3 decimals) but we want the final result + // to be 100.00E+00 (5 digits), so for the following fill routines + // below to work correctly append a zero decimal. + /* TODO: this is awkward, could an engineering notation mode be + * introduced to rtl_math_doubleToUString()? */ + sStr.truncate( sStr.indexOf('E') ); + if (sStr[0] == '1' && cFirstDigit != '1') + sStr.append('0'); + } + + // cut any decimal delimiter + sal_Int32 index = 0; + + while((index = sStr.indexOf('.', index)) >= 0) + { + if (nDecPos < 0) + nDecPos = index; + sStr.remove(index, 1); + } + } + + sal_uInt16 j = nCnt-1; // Last symbol + sal_Int32 k; // Position in ExpStr + sal_Int32 nZeros = 0; // Erase leading zeros + + bRes |= ImpNumberFill(ExpStr, fNumber, k, j, nIx, NF_SYMBOLTYPE_EXP); + + while (nZeros < k && ExpStr[nZeros] == '0') + { + ++nZeros; + } + if (nZeros) + { + ExpStr.remove( 0, nZeros); + } + + bool bCont = true; + + if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_EXP) + { + const OUString& rStr = rInfo.sStrArray[j]; + if (nExpSign == -1) + { + ExpStr.insert(0, '-'); + } + else if (rStr.getLength() > 1 && rStr[1] == '+') + { + ExpStr.insert(0, '+'); + } + ExpStr.insert(0, rStr[0]); + if ( j ) + { + j--; + } + else + { + bCont = false; + } + } + // Continue main number: + if ( !bCont ) + { + sStr.truncate(); + } + else + { + bRes |= ImpDecimalFill(sStr, fNumber, nDecPos, j, nIx, false); + } + + if (bSign) + { + sStr.insert(0, '-'); + } + sStr.append(ExpStr); + + return bRes; +} + +double SvNumberformat::GetRoundFractionValue ( double fNumber ) const +{ + sal_uInt16 nIx = GetSubformatIndex ( fNumber ); + double fIntPart = 0.0; // integer part of fraction + sal_Int64 nFrac = 0, nDiv = 1; // numerator and denominator + double fSign = (fNumber < 0.0) ? -1.0 : 1.0; + // fNumber is modified in ImpGetFractionElements to absolute fractional part + ImpGetFractionElements ( fNumber, nIx, fIntPart, nFrac, nDiv ); + if ( nDiv > 0 ) + return fSign * ( fIntPart + static_cast(nFrac) / static_cast(nDiv) ); + else + return fSign * fIntPart; +} + +void SvNumberformat::ImpGetFractionElements ( double& fNumber, sal_uInt16 nIx, + double& fIntPart, sal_Int64& nFrac, sal_Int64& nDiv ) const +{ + if ( fNumber < 0.0 ) + fNumber = -fNumber; + fIntPart = floor(fNumber); // Integral part + fNumber -= fIntPart; // Fractional part + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + sal_Int64 nForcedDiv = lcl_GetDenominatorString( rInfo, NumFor[nIx].GetCount() ).toInt32(); + if( nForcedDiv > 0 ) + { // Forced Denominator + nDiv = nForcedDiv; + nFrac = static_cast(floor ( fNumber * nDiv )); + double fFracNew = static_cast(nFrac) / static_cast(nDiv); + double fFracNew1 = static_cast(nFrac + 1) / static_cast(nDiv); + double fDiff = fNumber - fFracNew; + if( fDiff > ( fFracNew1 - fNumber ) ) + { + nFrac++; + } + } + else // Calculated Denominator + { + nDiv = 1; + sal_Int64 nBasis = static_cast(floor( pow(10.0,rInfo.nCntExp))) - 1; // 9, 99, 999 ,... + sal_Int64 nFracPrev = 1, nDivPrev = 0, nFracNext, nDivNext, nPartialDenom; + double fRemainder = fNumber; + + // Use continued fraction representation of fNumber + // See https://en.wikipedia.org/wiki/Continued_fraction#Best_rational_approximations + while ( fRemainder > 0.0 ) + { + double fTemp = 1.0 / fRemainder; // 64bits precision required when fRemainder is very weak + nPartialDenom = static_cast(floor(fTemp)); // due to floating point notation with double precision + fRemainder = fTemp - static_cast(nPartialDenom); + nDivNext = nPartialDenom * nDiv + nDivPrev; + if ( nDivNext <= nBasis ) // continue loop + { + nFracNext = nPartialDenom * nFrac + nFracPrev; + nFracPrev = nFrac; + nFrac = nFracNext; + nDivPrev = nDiv; + nDiv = nDivNext; + } + else // calculate collateral fraction and exit + { + sal_Int64 nCollat = (nBasis - nDivPrev) / nDiv; + if ( 2 * nCollat >= nPartialDenom ) + { + sal_Int64 nFracTest = nCollat * nFrac + nFracPrev; + sal_Int64 nDivTest = nCollat * nDiv + nDivPrev; + double fSign = (static_cast(nFrac) > fNumber * static_cast(nDiv))?1.0:-1.0; + if ( fSign * ( double(nFrac * nDivTest + nDiv * nFracTest) - 2.0 * double(nDiv * nDivTest) * fNumber ) > 0.0 ) + { + nFrac = nFracTest; + nDiv = nDivTest; + } + } + fRemainder = 0.0; // exit while loop + } + } + } + if (nFrac >= nDiv) + { + ++fIntPart; + nFrac = 0; + nDiv = ( nForcedDiv > 0 ) ? nForcedDiv : 1; + } +} + +bool SvNumberformat::ImpGetFractionOutput(double fNumber, + sal_uInt16 nIx, + OUStringBuffer& sBuff) +{ + bool bRes = false; + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + const sal_uInt16 nCnt = NumFor[nIx].GetCount(); + OUStringBuffer sStr, sFrac, sDiv; // Strings, value for Integral part Numerator and denominator + bool bSign = ( (fNumber < 0) && (nIx == 0) ); // sign Not in the ones at the end + const OUString sIntegerFormat = lcl_GetFractionIntegerString(rInfo, nCnt); + const OUString sNumeratorFormat = lcl_GetNumeratorString(rInfo, nCnt); + const OUString sDenominatorFormat = lcl_GetDenominatorString(rInfo, nCnt); + + sal_Int64 nFrac = 0, nDiv = 1; + double fNum = floor(fNumber); // Integral part + + if (fNum > D_MAX_U_INT32 || rInfo.nCntExp > 9) // Too large + { + sBuff = ImpSvNumberformatScan::sErrStr; + return false; + } + if (rInfo.nCntExp == 0) + { + SAL_WARN( "svl.numbers", "SvNumberformat:: Fraction, nCntExp == 0"); + sBuff.truncate(); + return false; + } + + ImpGetFractionElements( fNumber, nIx, fNum, nFrac, nDiv); + + if (rInfo.nCntPre == 0) // Improper fraction + { + double fNum1 = fNum * static_cast(nDiv) + static_cast(nFrac); + + if (fNum1 > D_MAX_INTEGER) + { + sBuff = ImpSvNumberformatScan::sErrStr; + return false; + } + nFrac = static_cast(floor(fNum1)); + } + else if (fNum == 0.0 && nFrac != 0) + { + } + else + { + char aBuf[100]; + sprintf( aBuf, "%.f", fNum ); // simple rounded integer (#100211# - checked) + sStr.appendAscii( aBuf ); + impTransliterate(sStr, NumFor[nIx].GetNatNum()); + } + bool bHideFraction = (rInfo.nCntPre > 0 && nFrac == 0 + && (sNumeratorFormat.indexOf('0') < 0) + && (sDenominatorFormat.indexOf('0') < 0 + || sDenominatorFormat.toInt32() > 0) ); + if ( bHideFraction ) + { + sDiv.truncate(); + } + else // if there are some '0' in format, force display of fraction + { + sFrac = ImpIntToString( nIx, nFrac ); + sDiv = ImpIntToString( nIx, nDiv ); + } + + sal_uInt16 j = nCnt-1; // Last symbol -> backwards + sal_Int32 k; // Denominator + + bRes |= ImpNumberFill(sDiv, fNumber, k, j, nIx, NF_SYMBOLTYPE_FRAC, true); + + bool bCont = true; + if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_FRAC) + { + if ( bHideFraction ) + { // do not insert blank for fraction if there is no '?' + if ( sNumeratorFormat.indexOf('?') >= 0 + || sDenominatorFormat.indexOf('?') >= 0 ) + sDiv.insert(0, ' '); + } + else + { + sDiv.insert(0, rInfo.sStrArray[j][0]); + } + if ( j ) + { + j--; + } + else + { + bCont = false; + } + } + // Further numerators: + if ( !bCont ) + { + sFrac.truncate(); + } + else + { + bRes |= ImpNumberFill(sFrac, fNumber, k, j, nIx, NF_SYMBOLTYPE_FRACBLANK); + bCont = false; // there is no integer part? + if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_FRACBLANK) + { + if ( j ) + { + if ( bHideFraction ) + { // '?' in any format force display of blank as delimiter + if ( sIntegerFormat.indexOf('?') >= 0 + || sNumeratorFormat.indexOf('?') >= 0 + || sDenominatorFormat.indexOf('?') >= 0 ) + { + for (sal_Int32 i = 0; i < rInfo.sStrArray[j].getLength(); i++) + sFrac.insert(0, ' '); + } + } + else + { + if ( fNum != 0.0 || sIntegerFormat.indexOf('0') >= 0 ) + sFrac.insert(0, rInfo.sStrArray[j]); // insert Blank string only if there are both integer and fraction + else + { + if ( sIntegerFormat.indexOf('?') >= 0 + || sNumeratorFormat.indexOf('?') >= 0 ) + { + for (sal_Int32 i = 0; i < rInfo.sStrArray[j].getLength(); i++) + sFrac.insert(0, ' '); + } + } + } + j--; + bCont = true; // Yes, there is an integer + } + else + sFrac.insert(0, rInfo.sStrArray[j]); + } + } + // Continue integer part + if ( !bCont ) + { + sStr.truncate(); + } + else + { + k = sStr.getLength(); // After last figure + bRes |= ImpNumberFillWithThousands(sStr, fNumber, k, j, nIx, + rInfo.nCntPre); + } + if (bSign && (nFrac != 0 || fNum != 0.0)) + { + sBuff.insert(0, '-'); // Not -0 + } + sBuff.append(sStr); + sBuff.append(sFrac); + sBuff.append(sDiv); + return bRes; +} + +sal_uInt16 SvNumberformat::ImpGetFractionOfSecondString( OUStringBuffer& rBuf, double fFractionOfSecond, + int nFractionDecimals, bool bAddOneRoundingDecimal, sal_uInt16 nIx, sal_uInt16 nMinimumInputLineDecimals ) +{ + if (!nFractionDecimals) + return 0; + + // nFractionDecimals+1 to not round up what Time::GetClock() carefully + // truncated. + rBuf.append( rtl::math::doubleToUString( fFractionOfSecond, rtl_math_StringFormat_F, + (bAddOneRoundingDecimal ? nFractionDecimals + 1 : nFractionDecimals), '.')); + rBuf.stripStart('0'); + rBuf.stripStart('.'); + if (bAddOneRoundingDecimal && rBuf.getLength() > nFractionDecimals) + rBuf.truncate( nFractionDecimals); // the digit appended because of nFractionDecimals+1 + if (nMinimumInputLineDecimals) + { + rBuf.stripEnd('0'); + for (sal_Int32 index = rBuf.getLength(); index < nMinimumInputLineDecimals; ++index) + { + rBuf.append('0'); + } + impTransliterate(rBuf, NumFor[nIx].GetNatNum()); + nFractionDecimals = rBuf.getLength(); + } + else + { + impTransliterate(rBuf, NumFor[nIx].GetNatNum()); + } + return static_cast(nFractionDecimals); +} + +bool SvNumberformat::ImpGetTimeOutput(double fNumber, + sal_uInt16 nIx, + OUStringBuffer& sBuff) +{ + using namespace ::com::sun::star::i18n; + bool bCalendarSet = false; + const double fNumberOrig = fNumber; + bool bRes = false; + bool bSign = false; + if (fNumber < 0.0) + { + fNumber = -fNumber; + if (nIx == 0) + { + bSign = true; + } + } + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + bool bInputLine; + sal_Int32 nCntPost; + if ( rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION && + 0 < rInfo.nCntPost && rInfo.nCntPost < 7 ) + { // round at 7 decimals (+5 of 86400 == 12 significant digits) + bInputLine = true; + nCntPost = 7; + } + else + { + bInputLine = false; + nCntPost = rInfo.nCntPost; + } + + OUStringBuffer sSecStr; + sal_Int32 nSecPos = 0; // For figure by figure processing + sal_uInt32 nHour, nMin, nSec; + if (!rInfo.bThousand) // No [] format + { + sal_uInt16 nCHour, nCMinute, nCSecond; + double fFractionOfSecond; + tools::Time::GetClock( fNumberOrig, nCHour, nCMinute, nCSecond, fFractionOfSecond, nCntPost); + nHour = nCHour; + nMin = nCMinute; + nSec = nCSecond; + nCntPost = ImpGetFractionOfSecondString( sSecStr, fFractionOfSecond, nCntPost, true, nIx, + (bInputLine ? rInfo.nCntPost : 0)); + } + else + { + const double fTime = rtl::math::round( fNumber * 86400.0, int(nCntPost)); + if (bSign && fTime == 0.0) + { + bSign = false; // Not -00:00:00 + } + if (fTime > D_MAX_U_INT32) + { + sBuff = ImpSvNumberformatScan::sErrStr; + return false; + } + sal_uInt32 nSeconds = static_cast(fTime); + + nCntPost = ImpGetFractionOfSecondString( sSecStr, fTime - nSeconds, nCntPost, false, nIx, + (bInputLine ? rInfo.nCntPost : 0)); + + if (rInfo.nThousand == 3) // [ss] + { + nHour = 0; + nMin = 0; + nSec = nSeconds; + } + else if (rInfo.nThousand == 2) // [mm]:ss + { + nHour = 0; + nMin = nSeconds / 60; + nSec = nSeconds % 60; + } + else if (rInfo.nThousand == 1) // [hh]:mm:ss + { + nHour = nSeconds / 3600; + nMin = (nSeconds%3600) / 60; + nSec = nSeconds%60; + } + else + { + // TODO What should these be set to? + nHour = 0; + nMin = 0; + nSec = 0; + } + } + + sal_Unicode cAmPm = ' '; // a or p + if (rInfo.nCntExp) // AM/PM + { + if (nHour == 0) + { + nHour = 12; + cAmPm = 'a'; + } + else if (nHour < 12) + { + cAmPm = 'a'; + } + else + { + cAmPm = 'p'; + if (nHour > 12) + { + nHour -= 12; + } + } + } + const sal_uInt16 nCnt = NumFor[nIx].GetCount(); + for (sal_uInt16 i = 0; i < nCnt; i++) + { + sal_Int32 nLen; + switch (rInfo.nTypeArray[i]) + { + case NF_SYMBOLTYPE_STAR: + if( bStarFlag ) + { + bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]); + } + break; + case NF_SYMBOLTYPE_BLANK: + if (rInfo.sStrArray[i].getLength() >= 2) + InsertBlanks(sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] ); + break; + case NF_SYMBOLTYPE_STRING: + case NF_SYMBOLTYPE_CURRENCY: + case NF_SYMBOLTYPE_DATESEP: + case NF_SYMBOLTYPE_TIMESEP: + case NF_SYMBOLTYPE_TIME100SECSEP: + sBuff.append(rInfo.sStrArray[i]); + break; + case NF_SYMBOLTYPE_DIGIT: + nLen = ( bInputLine && i > 0 && + (rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING || + rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_TIME100SECSEP) ? + nCntPost : rInfo.sStrArray[i].getLength() ); + for (sal_Int32 j = 0; j < nLen && nSecPos < nCntPost && nSecPos < sSecStr.getLength(); ++j) + { + sBuff.append(sSecStr[nSecPos]); + nSecPos++; + } + break; + case NF_KEY_AMPM: // AM/PM + if ( !bCalendarSet ) + { + double fDiff = DateTime(rScan.GetNullDate()) - GetCal().getEpochStart(); + fDiff += fNumberOrig; + GetCal().setLocalDateTime( fDiff ); + bCalendarSet = true; + } + if (cAmPm == 'a') + { + sBuff.append(GetCal().getDisplayName( + CalendarDisplayIndex::AM_PM, AmPmValue::AM, 0 )); + } + else + { + sBuff.append(GetCal().getDisplayName( + CalendarDisplayIndex::AM_PM, AmPmValue::PM, 0 )); + } + break; + case NF_KEY_AP: // A/P + if (cAmPm == 'a') + { + sBuff.append('a'); + } + else + { + sBuff.append('p'); + } + break; + case NF_KEY_MI: // M + sBuff.append(ImpIntToString( nIx, nMin )); + break; + case NF_KEY_MMI: // MM + sBuff.append(ImpIntToString( nIx, nMin, 2 )); + break; + case NF_KEY_H: // H + sBuff.append(ImpIntToString( nIx, nHour )); + break; + case NF_KEY_HH: // HH + sBuff.append(ImpIntToString( nIx, nHour, 2 )); + break; + case NF_KEY_S: // S + sBuff.append(ImpIntToString( nIx, nSec )); + break; + case NF_KEY_SS: // SS + sBuff.append(ImpIntToString( nIx, nSec, 2 )); + break; + default: + break; + } + } + if (bSign && rInfo.bThousand) + { + sBuff.insert(0, '-'); + } + return bRes; +} + + +/** If a day of month occurs within the format, the month name is in possessive + genitive case if the day follows the month, and partitive case if the day + precedes the month. If there is no day of month the nominative case (noun) + is returned. Also if the month is immediately preceded or followed by a + literal string other than space and not followed by a comma, the nominative + name is used, this prevents duplicated casing for MMMM\t\a and such in + documents imported from (e.g. Finnish) Excel or older LibO/OOo releases. + */ + +// IDEA: instead of eCodeType pass the index to nTypeArray and restrict +// inspection of month name around that one, that would enable different month +// cases in one format. Though probably the most rare use case ever... + +sal_Int32 SvNumberformat::ImpUseMonthCase( int & io_nState, const ImpSvNumFor& rNumFor, NfKeywordIndex eCodeType ) +{ + using namespace ::com::sun::star::i18n; + if (!io_nState) + { + bool bMonthSeen = false; + bool bDaySeen = false; + const ImpSvNumberformatInfo& rInfo = rNumFor.Info(); + const sal_uInt16 nCount = rNumFor.GetCount(); + for (sal_uInt16 i = 0; i < nCount && io_nState == 0; ++i) + { + sal_Int32 nLen; + switch (rInfo.nTypeArray[i]) + { + case NF_KEY_D : + case NF_KEY_DD : + if (bMonthSeen) + { + io_nState = 2; + } + else + { + bDaySeen = true; + } + break; + case NF_KEY_MMM: + case NF_KEY_MMMM: + case NF_KEY_MMMMM: + if ((i < nCount-1 && + rInfo.nTypeArray[i+1] == NF_SYMBOLTYPE_STRING && + // Literal following, not empty, space nor comma. + !rInfo.sStrArray[i+1].isEmpty() && + rInfo.sStrArray[i+1][0] != ' ' && rInfo.sStrArray[i+1][0] != ',') || + (i > 0 && rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING && + ((nLen = rInfo.sStrArray[i-1].getLength()) > 0) && + // Literal preceding, not space. + rInfo.sStrArray[i-1][nLen-1] != ' ')) + { + io_nState = 1; + } + else if (bDaySeen) + { + io_nState = 3; + } + else + { + bMonthSeen = true; + } + break; + } + } + if (io_nState == 0) + { + io_nState = 1; // No day of month + } + } + switch (io_nState) + { + case 1: + // No day of month or forced nominative + switch (eCodeType) + { + case NF_KEY_MMM: + return CalendarDisplayCode::SHORT_MONTH_NAME; + case NF_KEY_MMMM: + return CalendarDisplayCode::LONG_MONTH_NAME; + case NF_KEY_MMMMM: + return CalendarDisplayCode::NARROW_MONTH_NAME; + default: + ; // nothing + } + break; + case 2: + // Day of month follows month (the month's 17th) + switch (eCodeType) + { + case NF_KEY_MMM: + return CalendarDisplayCode::SHORT_GENITIVE_MONTH_NAME; + case NF_KEY_MMMM: + return CalendarDisplayCode::LONG_GENITIVE_MONTH_NAME; + case NF_KEY_MMMMM: + return CalendarDisplayCode::NARROW_GENITIVE_MONTH_NAME; + default: + ; // Nothing + } + break; + case 3: + // Day of month precedes month (17 of month) + switch (eCodeType) + { + case NF_KEY_MMM: + return CalendarDisplayCode::SHORT_PARTITIVE_MONTH_NAME; + case NF_KEY_MMMM: + return CalendarDisplayCode::LONG_PARTITIVE_MONTH_NAME; + case NF_KEY_MMMMM: + return CalendarDisplayCode::NARROW_PARTITIVE_MONTH_NAME; + default: + ; // nothing + } + break; + } + SAL_WARN( "svl.numbers", "ImpUseMonthCase: unhandled keyword index eCodeType"); + return CalendarDisplayCode::LONG_MONTH_NAME; +} + + +bool SvNumberformat::ImpIsOtherCalendar( const ImpSvNumFor& rNumFor ) const +{ + if ( GetCal().getUniqueID() != GREGORIAN ) + { + return false; + } + const ImpSvNumberformatInfo& rInfo = rNumFor.Info(); + const sal_uInt16 nCnt = rNumFor.GetCount(); + sal_uInt16 i; + for ( i = 0; i < nCnt; i++ ) + { + switch ( rInfo.nTypeArray[i] ) + { + case NF_SYMBOLTYPE_CALENDAR : + return false; + case NF_KEY_EC : + case NF_KEY_EEC : + case NF_KEY_R : + case NF_KEY_RR : + case NF_KEY_AAA : + case NF_KEY_AAAA : + case NF_KEY_G : + case NF_KEY_GG : + case NF_KEY_GGG : + return true; + } + } + return false; +} + +void SvNumberformat::SwitchToOtherCalendar( OUString& rOrgCalendar, + double& fOrgDateTime ) const +{ + CalendarWrapper& rCal = GetCal(); + if ( rCal.getUniqueID() != GREGORIAN ) + return; + + using namespace ::com::sun::star::i18n; + const css::uno::Sequence< OUString > xCals = rCal.getAllCalendars( + rLoc().getLanguageTag().getLocale() ); + sal_Int32 nCnt = xCals.getLength(); + if ( nCnt <= 1 ) + return; + + auto pCal = std::find_if(xCals.begin(), xCals.end(), + [](const OUString& rCalName) { return rCalName != GREGORIAN; }); + if (pCal == xCals.end()) + return; + + if ( !rOrgCalendar.getLength() ) + { + rOrgCalendar = rCal.getUniqueID(); + fOrgDateTime = rCal.getDateTime(); + } + rCal.loadCalendar( *pCal, rLoc().getLanguageTag().getLocale() ); + rCal.setDateTime( fOrgDateTime ); +} + +void SvNumberformat::SwitchToGregorianCalendar( const OUString& rOrgCalendar, + double fOrgDateTime ) const +{ + CalendarWrapper& rCal = GetCal(); + if ( rOrgCalendar.getLength() && rCal.getUniqueID() != GREGORIAN ) + { + rCal.loadCalendar( GREGORIAN, rLoc().getLanguageTag().getLocale() ); + rCal.setDateTime( fOrgDateTime ); + } +} + +bool SvNumberformat::ImpFallBackToGregorianCalendar( OUString& rOrgCalendar, double& fOrgDateTime ) +{ + using namespace ::com::sun::star::i18n; + CalendarWrapper& rCal = GetCal(); + if ( rCal.getUniqueID() != GREGORIAN ) + { + sal_Int16 nVal = rCal.getValue( CalendarFieldIndex::ERA ); + if ( nVal == 0 && rCal.getLoadedCalendar().Eras[0].ID == "Dummy" ) + { + if ( !rOrgCalendar.getLength() ) + { + rOrgCalendar = rCal.getUniqueID(); + fOrgDateTime = rCal.getDateTime(); + } + else if ( rOrgCalendar == GREGORIAN ) + { + rOrgCalendar.clear(); + } + rCal.loadCalendar( GREGORIAN, rLoc().getLanguageTag().getLocale() ); + rCal.setDateTime( fOrgDateTime ); + return true; + } + } + return false; +} + + +#ifdef THE_FUTURE +/* XXX NOTE: even if the ImpSwitchToSpecifiedCalendar method is currently + * unused please don't remove it, it would be needed by + * SwitchToSpecifiedCalendar(), see comment in + * ImpSvNumberInputScan::GetDateRef() */ + +bool SvNumberformat::ImpSwitchToSpecifiedCalendar( OUString& rOrgCalendar, + double& fOrgDateTime, + const ImpSvNumFor& rNumFor ) const +{ + const ImpSvNumberformatInfo& rInfo = rNumFor.Info(); + const sal_uInt16 nCnt = rNumFor.GetCount(); + for ( sal_uInt16 i = 0; i < nCnt; i++ ) + { + if ( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_CALENDAR ) + { + CalendarWrapper& rCal = GetCal(); + if ( !rOrgCalendar.getLength() ) + { + rOrgCalendar = rCal.getUniqueID(); + fOrgDateTime = rCal.getDateTime(); + } + rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLocale() ); + rCal.setDateTime( fOrgDateTime ); + return true; + } + } + return false; +} +#endif + +// static +void SvNumberformat::ImpAppendEraG( OUStringBuffer& OutString, + const CalendarWrapper& rCal, + sal_Int16 nNatNum ) +{ + using namespace ::com::sun::star::i18n; + if ( rCal.getUniqueID() == "gengou" ) + { + sal_Unicode cEra; + sal_Int16 nVal = rCal.getValue( CalendarFieldIndex::ERA ); + switch ( nVal ) + { + case 1: + cEra = 'M'; + break; + case 2: + cEra = 'T'; + break; + case 3: + cEra = 'S'; + break; + case 4: + cEra = 'H'; + break; + case 5: + cEra = 'R'; + break; + default: + cEra = '?'; + break; + } + OutString.append(cEra); + } + else + { + OutString.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum )); + } +} + +bool SvNumberformat::ImpIsIso8601( const ImpSvNumFor& rNumFor ) const +{ + bool bIsIso = false; + if (eType & SvNumFormatType::DATE) + { + enum State + { + eNone, + eAtYear, + eAtSep1, + eAtMonth, + eAtSep2, + eNotIso + }; + State eState = eNone; + auto & rTypeArray = rNumFor.Info().nTypeArray; + sal_uInt16 nCnt = rNumFor.GetCount(); + for (sal_uInt16 i=0; i < nCnt && !bIsIso && eState != eNotIso; ++i) + { + switch ( rTypeArray[i] ) + { + case NF_KEY_YY: // two digits not strictly ISO 8601 + case NF_KEY_YYYY: + if (eState != eNone) + { + eState = eNotIso; + } + else + { + eState = eAtYear; + } + break; + case NF_KEY_M: // single digit not strictly ISO 8601 + case NF_KEY_MM: + if (eState != eAtSep1) + { + eState = eNotIso; + } + else + { + eState = eAtMonth; + } + break; + case NF_KEY_D: // single digit not strictly ISO 8601 + case NF_KEY_DD: + if (eState != eAtSep2) + { + eState = eNotIso; + } + else + { + bIsIso = true; + } + break; + case NF_SYMBOLTYPE_STRING: + case NF_SYMBOLTYPE_DATESEP: + if (rNumFor.Info().sStrArray[i] == "-") + { + if (eState == eAtYear) + { + eState = eAtSep1; + } + else if (eState == eAtMonth) + { + eState = eAtSep2; + } + else + { + eState = eNotIso; + } + } + else + { + eState = eNotIso; + } + break; + default: + eState = eNotIso; + } + } + } + else + { + SAL_WARN( "svl.numbers", "SvNumberformat::ImpIsIso8601: no date" ); + } + return bIsIso; +} + +static bool lcl_hasEra( const ImpSvNumFor& rNumFor ) +{ + const ImpSvNumberformatInfo& rInfo = rNumFor.Info(); + const sal_uInt16 nCnt = rNumFor.GetCount(); + for ( sal_uInt16 i = 0; i < nCnt; i++ ) + { + switch ( rInfo.nTypeArray[i] ) + { + case NF_KEY_RR : + case NF_KEY_G : + case NF_KEY_GG : + case NF_KEY_GGG : + return true; + } + } + return false; +} + +static bool lcl_isSignedYear( const CalendarWrapper& rCal, const ImpSvNumFor& rNumFor ) +{ + return rCal.getValue( css::i18n::CalendarFieldIndex::ERA ) == 0 && + rCal.getUniqueID() == GREGORIAN && !lcl_hasEra( rNumFor ); +} + +/* XXX: if needed this could be stripped from rEpochStart and diff adding and + * moved to tools' DateTime to be reused elsewhere. */ +static bool lcl_getValidDate( const DateTime& rNullDate, const DateTime& rEpochStart, double& fNumber ) +{ + static const DateTime aCE( Date(1,1,1)); + static const DateTime aMin( Date(1,1, SAL_MIN_INT16)); + static const DateTime aMax( Date(31,12, SAL_MAX_INT16), tools::Time(23,59,59, tools::Time::nanoSecPerSec - 1)); + static const double fMin = aMin - aCE; + static const double fMax = aMax - aCE; + // Value must be representable in our tools::Date proleptic Gregorian + // calendar as well. + const double fOff = (rNullDate - aCE) + fNumber; + // Add diff between epochs to serial date number. + const double fDiff = rNullDate - rEpochStart; + fNumber += fDiff; + return fMin <= fOff && fOff <= fMax; +} + +bool SvNumberformat::ImpGetDateOutput(double fNumber, + sal_uInt16 nIx, + OUStringBuffer& sBuff) +{ + using namespace ::com::sun::star::i18n; + bool bRes = false; + + CalendarWrapper& rCal = GetCal(); + if (!lcl_getValidDate( rScan.GetNullDate(), rCal.getEpochStart(), fNumber)) + { + sBuff = ImpSvNumberformatScan::sErrStr; + return false; + } + rCal.setLocalDateTime( fNumber ); + int nUseMonthCase = 0; // Not decided yet + OUString aOrgCalendar; // empty => not changed yet + + double fOrgDateTime(0.0); + bool bOtherCalendar = ImpIsOtherCalendar( NumFor[nIx] ); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + if ( ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime ) ) + { + bOtherCalendar = false; + } + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + const sal_uInt16 nCnt = NumFor[nIx].GetCount(); + sal_Int16 nNatNum = NumFor[nIx].GetNatNum().GetNatNum(); + OUString aStr; + + // NatNum12: if the date format contains more than a date + // field, it needs to specify in NatNum12 argument + // which date element needs special formatting: + // + // '[NatNum12 ordinal-number]D' -> "1st" + // '[NatNum12 D=ordinal-number]D" of "MMMM' -> "1st of April" + // '[NatNum12 D=ordinal]D" of "MMMM' -> "first of April" + // '[NatNum12 YYYY=year,D=ordinal]D" of "MMMM", "YYYY' -> "first of April, nineteen ninety" + // + // Note: set only for YYYY, MMMM, M, DDDD, D and NNN/AAAA in date formats. + // Additionally for MMMMM, MMM, DDD and NN/AA to support at least + // capitalize, upper, lower, title. + // XXX It's possible to extend this for other keywords and date + time + // combinations, as required. + + bool bUseSpellout = NatNumTakesParameters(nNatNum) && + (nCnt == 1 || NumFor[nIx].GetNatNum().GetParams().indexOf('=') > -1); + + for (sal_uInt16 i = 0; i < nCnt; i++) + { + switch (rInfo.nTypeArray[i]) + { + case NF_SYMBOLTYPE_CALENDAR : + if ( !aOrgCalendar.getLength() ) + { + aOrgCalendar = rCal.getUniqueID(); + fOrgDateTime = rCal.getDateTime(); + } + rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLanguageTag().getLocale() ); + rCal.setDateTime( fOrgDateTime ); + ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + break; + case NF_SYMBOLTYPE_STAR: + if( bStarFlag ) + { + bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]); + } + break; + case NF_SYMBOLTYPE_BLANK: + if (rInfo.sStrArray[i].getLength() >= 2) + InsertBlanks( sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] ); + break; + case NF_SYMBOLTYPE_STRING: + case NF_SYMBOLTYPE_CURRENCY: + case NF_SYMBOLTYPE_DATESEP: + case NF_SYMBOLTYPE_TIMESEP: + case NF_SYMBOLTYPE_TIME100SECSEP: + sBuff.append(rInfo.sStrArray[i]); + break; + case NF_KEY_M: // M + aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_MONTH, nNatNum ); + // NatNum12: support variants of preposition, suffixation or article + // for example, Catalan "de març", but "d'abril" etc. + if ( bUseSpellout ) + { + aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); + } + sBuff.append(aStr); + break; + case NF_KEY_MM: // MM + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_MONTH, nNatNum )); + break; + case NF_KEY_MMM: // MMM + case NF_KEY_MMMM: // MMMM + case NF_KEY_MMMMM: // MMMMM + // NatNum12: support variants of preposition, suffixation or + // article, or capitalize, upper, lower, title. + // Note: result of the "spell out" conversion can depend from the optional + // PartitiveMonths or GenitiveMonths defined in the locale data, + // see description of ImpUseMonthCase(), and locale data in + // i18npool/source/localedata/data/ and libnumbertext + aStr = rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx], + static_cast(rInfo.nTypeArray[i])), + nNatNum); + if ( bUseSpellout ) + { + aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); + } + sBuff.append(aStr); + break; + case NF_KEY_Q: // Q + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_QUARTER, nNatNum )); + break; + case NF_KEY_QQ: // QQ + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_QUARTER, nNatNum )); + break; + case NF_KEY_D: // D + aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY, nNatNum ); + // NatNum12: support variants of preposition, suffixation or article + if ( bUseSpellout ) + { + aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); + } + sBuff.append(aStr); + break; + case NF_KEY_DD: // DD + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY, nNatNum )); + break; + case NF_KEY_DDD: // DDD + if ( bOtherCalendar ) + { + SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + } + aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum ); + // NatNum12: support at least capitalize, upper, lower, title + if ( bUseSpellout ) + { + aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); + } + sBuff.append(aStr); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + break; + case NF_KEY_DDDD: // DDDD + if ( bOtherCalendar ) + { + SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + } + aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ); + // NatNum12: support variants of preposition, suffixation or article + if ( bUseSpellout ) + { + aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); + } + sBuff.append(aStr); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + break; + case NF_KEY_YY: // YY + if ( bOtherCalendar ) + { + SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + } + // Prepend a minus sign if Gregorian BCE and era is not displayed. + if (lcl_isSignedYear( rCal, NumFor[nIx] )) + { + sBuff.append('-'); + } + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum )); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + break; + case NF_KEY_YYYY: // YYYY + if ( bOtherCalendar ) + { + SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + } + // Prepend a minus sign if Gregorian BCE and era is not displayed. + if (lcl_isSignedYear( rCal, NumFor[nIx] )) + { + sBuff.append('-'); + } + aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum ); + if (aStr.getLength() < 4 && !lcl_hasEra(NumFor[nIx])) + { + using namespace comphelper::string; + // Ensure that year consists of at least 4 digits, so it + // can be distinguished from 2 digits display and edited + // without suddenly being hit by the 2-digit year magic. + OUStringBuffer aBuf; + padToLength(aBuf, 4 - aStr.getLength(), '0'); + impTransliterate(aBuf, NumFor[nIx].GetNatNum()); + aBuf.append(aStr); + aStr = aBuf.makeStringAndClear(); + } + // NatNum12: support variants of preposition, suffixation or article + if ( bUseSpellout ) + { + aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); + } + sBuff.append(aStr); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + break; + case NF_KEY_EC: // E + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum )); + break; + case NF_KEY_EEC: // EE + case NF_KEY_R: // R + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum )); + break; + case NF_KEY_NN: // NN + case NF_KEY_AAA: // AAA + aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum ); + // NatNum12: support at least capitalize, upper, lower, title + if ( bUseSpellout ) + { + aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); + } + sBuff.append(aStr); + break; + case NF_KEY_NNN: // NNN + case NF_KEY_AAAA: // AAAA + aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ); + // NatNum12: support variants of preposition, suffixation or article + if ( bUseSpellout ) + { + aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]); + } + sBuff.append(aStr); + break; + case NF_KEY_NNNN: // NNNN + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum )); + sBuff.append(rLoc().getLongDateDayOfWeekSep()); + break; + case NF_KEY_WW : // WW + sBuff.append(ImpIntToString( nIx, + rCal.getValue( CalendarFieldIndex::WEEK_OF_YEAR ))); + break; + case NF_KEY_G: // G + ImpAppendEraG(sBuff, rCal, nNatNum ); + break; + case NF_KEY_GG: // GG + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum )); + break; + case NF_KEY_GGG: // GGG + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_ERA, nNatNum )); + break; + case NF_KEY_RR: // RR => GGGEE + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR_AND_ERA, nNatNum )); + break; + } + } + if ( aOrgCalendar.getLength() ) + { + rCal.loadCalendar( aOrgCalendar, rLoc().getLanguageTag().getLocale() ); // restore calendar + } + return bRes; +} + +bool SvNumberformat::ImpGetDateTimeOutput(double fNumber, + sal_uInt16 nIx, + OUStringBuffer& sBuff) +{ + using namespace ::com::sun::star::i18n; + bool bRes = false; + + CalendarWrapper& rCal = GetCal(); + if (!lcl_getValidDate( rScan.GetNullDate(), rCal.getEpochStart(), fNumber)) + { + sBuff = ImpSvNumberformatScan::sErrStr; + return false; + } + + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + bool bInputLine; + sal_Int32 nCntPost; + if ( rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION && + 0 < rInfo.nCntPost && rInfo.nCntPost < 7 ) + { + // round at 7 decimals (+5 of 86400 == 12 significant digits) + bInputLine = true; + nCntPost = 7; + } + else + { + bInputLine = false; + nCntPost = rInfo.nCntPost; + } + double fTime = (fNumber - floor( fNumber )) * 86400.0; + fTime = ::rtl::math::round( fTime, int(nCntPost) ); + if (fTime >= 86400.0) + { + // result of fNumber==x.999999999... rounded up, use correct date/time + fTime -= 86400.0; + fNumber = floor( fNumber + 0.5) + fTime; + } + rCal.setLocalDateTime( fNumber ); + + int nUseMonthCase = 0; // Not decided yet + OUString aOrgCalendar; // empty => not changed yet + double fOrgDateTime(0.0); + bool bOtherCalendar = ImpIsOtherCalendar( NumFor[nIx] ); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + if ( ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime ) ) + { + bOtherCalendar = false; + } + sal_Int16 nNatNum = NumFor[nIx].GetNatNum().GetNatNum(); + + OUStringBuffer sSecStr; + sal_Int32 nSecPos = 0; // For figure by figure processing + sal_uInt32 nHour, nMin, nSec; + if (!rInfo.bThousand) // No [] format + { + sal_uInt16 nCHour, nCMinute, nCSecond; + double fFractionOfSecond; + tools::Time::GetClock( fNumber, nCHour, nCMinute, nCSecond, fFractionOfSecond, nCntPost); + nHour = nCHour; + nMin = nCMinute; + nSec = nCSecond; + nCntPost = ImpGetFractionOfSecondString( sSecStr, fFractionOfSecond, nCntPost, true, nIx, + (bInputLine ? rInfo.nCntPost : 0)); + } + else + { + sal_uInt32 nSeconds = static_cast(floor( fTime )); + + nCntPost = ImpGetFractionOfSecondString( sSecStr, fTime - nSeconds, nCntPost, false, nIx, + (bInputLine ? rInfo.nCntPost : 0)); + + if (rInfo.nThousand == 3) // [ss] + { + nHour = 0; + nMin = 0; + nSec = nSeconds; + } + else if (rInfo.nThousand == 2) // [mm]:ss + { + nHour = 0; + nMin = nSeconds / 60; + nSec = nSeconds % 60; + } + else if (rInfo.nThousand == 1) // [hh]:mm:ss + { + nHour = nSeconds / 3600; + nMin = (nSeconds%3600) / 60; + nSec = nSeconds%60; + } + else + { + nHour = 0; // TODO What should these values be? + nMin = 0; + nSec = 0; + } + } + sal_Unicode cAmPm = ' '; // a or p + if (rInfo.nCntExp) // AM/PM + { + if (nHour == 0) + { + nHour = 12; + cAmPm = 'a'; + } + else if (nHour < 12) + { + cAmPm = 'a'; + } + else + { + cAmPm = 'p'; + if (nHour > 12) + { + nHour -= 12; + } + } + } + const sal_uInt16 nCnt = NumFor[nIx].GetCount(); + sal_Int32 nLen; + OUString aYear; + for (sal_uInt16 i = 0; i < nCnt; i++) + { + switch (rInfo.nTypeArray[i]) + { + case NF_SYMBOLTYPE_CALENDAR : + if ( !aOrgCalendar.getLength() ) + { + aOrgCalendar = rCal.getUniqueID(); + fOrgDateTime = rCal.getDateTime(); + } + rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLanguageTag().getLocale() ); + rCal.setDateTime( fOrgDateTime ); + ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + break; + case NF_SYMBOLTYPE_STAR: + if( bStarFlag ) + { + bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]); + } + break; + case NF_SYMBOLTYPE_BLANK: + if (rInfo.sStrArray[i].getLength() >= 2) + InsertBlanks( sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] ); + break; + case NF_SYMBOLTYPE_STRING: + case NF_SYMBOLTYPE_CURRENCY: + case NF_SYMBOLTYPE_DATESEP: + case NF_SYMBOLTYPE_TIMESEP: + case NF_SYMBOLTYPE_TIME100SECSEP: + sBuff.append(rInfo.sStrArray[i]); + break; + case NF_SYMBOLTYPE_DIGIT: + nLen = ( bInputLine && i > 0 && + (rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING || + rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_TIME100SECSEP) ? + nCntPost : rInfo.sStrArray[i].getLength() ); + for (sal_Int32 j = 0; j < nLen && nSecPos < nCntPost && nSecPos < sSecStr.getLength(); ++j) + { + sBuff.append(sSecStr[ nSecPos ]); + nSecPos++; + } + break; + case NF_KEY_AMPM: // AM/PM + if (cAmPm == 'a') + { + sBuff.append(rCal.getDisplayName( CalendarDisplayIndex::AM_PM, + AmPmValue::AM, 0 )); + } + else + { + sBuff.append(rCal.getDisplayName( CalendarDisplayIndex::AM_PM, + AmPmValue::PM, 0 )); + } + break; + case NF_KEY_AP: // A/P + if (cAmPm == 'a') + { + sBuff.append('a'); + } + else + { + sBuff.append('p'); + } + break; + case NF_KEY_MI: // M + sBuff.append(ImpIntToString( nIx, nMin )); + break; + case NF_KEY_MMI: // MM + sBuff.append(ImpIntToString( nIx, nMin, 2 )); + break; + case NF_KEY_H: // H + sBuff.append(ImpIntToString( nIx, nHour )); + break; + case NF_KEY_HH: // HH + sBuff.append(ImpIntToString( nIx, nHour, 2 )); + break; + case NF_KEY_S: // S + sBuff.append(ImpIntToString( nIx, nSec )); + break; + case NF_KEY_SS: // SS + sBuff.append(ImpIntToString( nIx, nSec, 2 )); + break; + case NF_KEY_M: // M + sBuff.append(rCal.getDisplayString( + CalendarDisplayCode::SHORT_MONTH, nNatNum )); + break; + case NF_KEY_MM: // MM + sBuff.append(rCal.getDisplayString( + CalendarDisplayCode::LONG_MONTH, nNatNum )); + break; + case NF_KEY_MMM: // MMM + sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx], + static_cast(rInfo.nTypeArray[i])), + nNatNum)); + break; + case NF_KEY_MMMM: // MMMM + sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx], + static_cast(rInfo.nTypeArray[i])), + nNatNum)); + break; + case NF_KEY_MMMMM: // MMMMM + sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx], + static_cast(rInfo.nTypeArray[i])), + nNatNum)); + break; + case NF_KEY_Q: // Q + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_QUARTER, nNatNum )); + break; + case NF_KEY_QQ: // QQ + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_QUARTER, nNatNum )); + break; + case NF_KEY_D: // D + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY, nNatNum )); + break; + case NF_KEY_DD: // DD + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY, nNatNum )); + break; + case NF_KEY_DDD: // DDD + if ( bOtherCalendar ) + { + SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + } + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum )); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + break; + case NF_KEY_DDDD: // DDDD + if ( bOtherCalendar ) + { + SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + } + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum )); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + break; + case NF_KEY_YY: // YY + if ( bOtherCalendar ) + { + SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + } + // Prepend a minus sign if Gregorian BCE and era is not displayed. + if (lcl_isSignedYear( rCal, NumFor[nIx] )) + { + sBuff.append('-'); + } + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum )); + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + break; + case NF_KEY_YYYY: // YYYY + if ( bOtherCalendar ) + { + SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime ); + } + // Prepend a minus sign if Gregorian BCE and era is not displayed. + if (lcl_isSignedYear( rCal, NumFor[nIx] )) + { + sBuff.append('-'); + } + aYear = rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum ); + if (aYear.getLength() < 4 && !lcl_hasEra(NumFor[nIx])) + { + using namespace comphelper::string; + // Ensure that year consists of at least 4 digits, so it + // can be distinguished from 2 digits display and edited + // without suddenly being hit by the 2-digit year magic. + OUStringBuffer aBuf; + padToLength(aBuf, 4 - aYear.getLength(), '0'); + impTransliterate(aBuf, NumFor[nIx].GetNatNum()); + aBuf.append(aYear); + sBuff.append(aBuf); + } + else + { + sBuff.append(aYear); + } + if ( bOtherCalendar ) + { + SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime ); + } + break; + case NF_KEY_EC: // E + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum )); + break; + case NF_KEY_EEC: // EE + case NF_KEY_R: // R + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum )); + break; + case NF_KEY_NN: // NN + case NF_KEY_AAA: // AAA + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum )); + break; + case NF_KEY_NNN: // NNN + case NF_KEY_AAAA: // AAAA + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum )); + break; + case NF_KEY_NNNN: // NNNN + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum )); + sBuff.append(rLoc().getLongDateDayOfWeekSep()); + break; + case NF_KEY_WW : // WW + sBuff.append(ImpIntToString( nIx, rCal.getValue( CalendarFieldIndex::WEEK_OF_YEAR ))); + break; + case NF_KEY_G: // G + ImpAppendEraG( sBuff, rCal, nNatNum ); + break; + case NF_KEY_GG: // GG + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum )); + break; + case NF_KEY_GGG: // GGG + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_ERA, nNatNum )); + break; + case NF_KEY_RR: // RR => GGGEE + sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR_AND_ERA, nNatNum )); + break; + } + } + if ( aOrgCalendar.getLength() ) + { + rCal.loadCalendar( aOrgCalendar, rLoc().getLanguageTag().getLocale() ); // restore calendar + } + return bRes; +} + +bool SvNumberformat::ImpGetLogicalOutput(double fNumber, + sal_uInt16 nIx, + OUStringBuffer& sStr) +{ + bool bRes = false; + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + const sal_uInt16 nCnt = NumFor[nIx].GetCount(); + for (sal_uInt16 j = 0; j < nCnt; ++j) + { + switch (rInfo.nTypeArray[j]) + { + case NF_KEY_BOOLEAN: + sStr.append( fNumber ? rScan.GetTrueString() : rScan.GetFalseString()); + break; + case NF_SYMBOLTYPE_STRING: + sStr.append( rInfo.sStrArray[j]); + break; + } + } + impTransliterate(sStr, NumFor[nIx].GetNatNum()); + return bRes; +} + +bool SvNumberformat::ImpGetNumberOutput(double fNumber, + sal_uInt16 nIx, + OUStringBuffer& sStr) +{ + bool bRes = false; + bool bSign; + if (fNumber < 0.0) + { + bSign = (nIx == 0); // Not in the ones at the back; + fNumber = -fNumber; + } + else + { + bSign = false; + if ( std::signbit( fNumber ) ) + { + fNumber = -fNumber; // yes, -0.0 is possible, eliminate '-' + } + } + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + if (rInfo.eScannedType == SvNumFormatType::PERCENT) + { + if (fNumber < D_MAX_D_BY_100) + { + fNumber *= 100.0; + } + else + { + sStr = ImpSvNumberformatScan::sErrStr; + return false; + } + } + sal_uInt16 i, j; + sal_Int32 nDecPos = -1; + bool bInteger = false; + if ( rInfo.nThousand != FLAG_STANDARD_IN_FORMAT ) + { + // Special formatting only if no GENERAL keyword in format code + const sal_uInt16 nThousand = rInfo.nThousand; + tools::Long nPrecExp; + for (i = 0; i < nThousand; i++) + { + if (fNumber > D_MIN_M_BY_1000) + { + fNumber /= 1000.0; + } + else + { + fNumber = 0.0; + } + } + if (fNumber > 0.0) + { + nPrecExp = GetPrecExp( fNumber ); + } + else + { + nPrecExp = 0; + } + if (rInfo.nCntPost) // Decimal places + { + if ((rInfo.nCntPost + nPrecExp) > 15 && nPrecExp < 15) + { + sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, 15-nPrecExp, '.'); + for (tools::Long l = 15-nPrecExp; l < static_cast(rInfo.nCntPost); l++) + { + sStr.append('0'); + } + } + else + { + sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, rInfo.nCntPost, '.' ); + } + sStr.stripStart('0'); // Strip leading zeros + } + else if (fNumber == 0.0) // Null + { + // Nothing to be done here, keep empty string sStr, + // ImpNumberFillWithThousands does the rest + } + else // Integer + { + sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, 0, '.'); + sStr.stripStart('0'); // Strip leading zeros + } + nDecPos = sStr.indexOf('.' ); + if ( nDecPos >= 0) + { + const sal_Unicode* p = sStr.getStr() + nDecPos; + while ( *++p == '0' ) + ; + if ( !*p ) + { + bInteger = true; + } + sStr.remove( nDecPos, 1 ); // Remove . + } + if (bSign && (sStr.isEmpty() || checkForAll0s(sStr))) // Only 00000 + { + bSign = false; // Not -0.00 + } + } // End of != FLAG_STANDARD_IN_FORMAT + + // Edit backwards: + j = NumFor[nIx].GetCount()-1; // Last symbol + // Decimal places: + bRes |= ImpDecimalFill( sStr, fNumber, nDecPos, j, nIx, bInteger ); + if (bSign) + { + sStr.insert(0, '-'); + } + impTransliterate(sStr, NumFor[nIx].GetNatNum()); + return bRes; +} + +bool SvNumberformat::ImpDecimalFill( OUStringBuffer& sStr, // number string + double& rNumber, // number + sal_Int32 nDecPos, // decimals start + sal_uInt16 j, // symbol index within format code + sal_uInt16 nIx, // subformat index + bool bInteger) // is integer +{ + bool bRes = false; + bool bFilled = false; // Was filled? + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + sal_Int32 k = sStr.getLength(); // After last figure + // Decimal places: + if (rInfo.nCntPost > 0) + { + bool bTrailing = true; // Trailing zeros? + short nType; + while (j > 0 && // Backwards + (nType = rInfo.nTypeArray[j]) != NF_SYMBOLTYPE_DECSEP) + { + switch ( nType ) + { + case NF_SYMBOLTYPE_STAR: + if( bStarFlag ) + { + bRes = lcl_insertStarFillChar( sStr, k, rInfo.sStrArray[j]); + } + break; + case NF_SYMBOLTYPE_BLANK: + if (rInfo.sStrArray[j].getLength() >= 2) + /*k = */ InsertBlanks(sStr, k, rInfo.sStrArray[j][1] ); + break; + case NF_SYMBOLTYPE_STRING: + case NF_SYMBOLTYPE_CURRENCY: + case NF_SYMBOLTYPE_PERCENT: + sStr.insert(k, rInfo.sStrArray[j]); + break; + case NF_SYMBOLTYPE_THSEP: + if (rInfo.nThousand == 0) + { + sStr.insert(k, rInfo.sStrArray[j]); + } + break; + case NF_SYMBOLTYPE_DIGIT: + { + const OUString& rStr = rInfo.sStrArray[j]; + const sal_Unicode* p1 = rStr.getStr(); + const sal_Unicode* p = p1 + rStr.getLength(); + // In case the number of decimals passed are less than the + // "digits" given, append trailing '0' characters, which here + // means insert them because literal strings may have been + // appended already. If they weren't to be '0' characters + // they'll be changed below, as if decimals with trailing zeros + // were passed. + if (nDecPos >= 0 && nDecPos <= k) + { + sal_Int32 nAppend = rStr.getLength() - (k - nDecPos); + while (nAppend-- > 0) + { + sStr.insert( k++, '0'); + } + } + while (k && p1 < p--) + { + const sal_Unicode c = *p; + k--; + if ( sStr[k] != '0' ) + { + bTrailing = false; + bFilled = true; + } + if (bTrailing) + { + if ( c == '0' ) + { + bFilled = true; + } + else if ( c == '-' ) + { + if ( bInteger ) + { + sStr[ k ] = '-'; + } + bFilled = true; + } + else if ( c == '?' ) + { + sStr[ k ] = ' '; + bFilled = true; + } + else if ( !bFilled ) // # + { + sStr.remove(k,1); + } + } + } // of for + break; + } // of case digi + case NF_KEY_CCC: // CCC currency + sStr.insert(k, rScan.GetCurAbbrev()); + break; + case NF_KEY_GENERAL: // Standard in the String + { + OUStringBuffer sNum; + ImpGetOutputStandard(rNumber, sNum); + sNum.stripStart('-'); + sStr.insert(k, sNum.makeStringAndClear()); + break; + } + default: + break; + } // of switch + j--; + } // of while + } // of decimal places + + bRes |= ImpNumberFillWithThousands(sStr, rNumber, k, j, nIx, // Fill with . if needed + rInfo.nCntPre, bFilled ); + + return bRes; +} + +bool SvNumberformat::ImpNumberFillWithThousands( OUStringBuffer& sBuff, // number string + double& rNumber, // number + sal_Int32 k, // position within string + sal_uInt16 j, // symbol index within format code + sal_uInt16 nIx, // subformat index + sal_Int32 nDigCnt, // count of integer digits in format + bool bAddDecSep) // add decimal separator if necessary +{ + bool bRes = false; + sal_Int32 nLeadingStringChars = 0; // inserted StringChars before number + sal_Int32 nDigitCount = 0; // count of integer digits from the right + bool bStop = false; + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + // no normal thousands separators if number divided by thousands + bool bDoThousands = (rInfo.nThousand == 0); + utl::DigitGroupingIterator aGrouping( GetFormatter().GetLocaleData()->getDigitGrouping()); + + while (!bStop) // backwards + { + if (j == 0) + { + bStop = true; + } + switch (rInfo.nTypeArray[j]) + { + case NF_SYMBOLTYPE_DECSEP: + aGrouping.reset(); + [[fallthrough]]; + case NF_SYMBOLTYPE_STRING: + case NF_SYMBOLTYPE_CURRENCY: + case NF_SYMBOLTYPE_PERCENT: + if ( rInfo.nTypeArray[j] != NF_SYMBOLTYPE_DECSEP || bAddDecSep ) + sBuff.insert(k, rInfo.sStrArray[j]); + if ( k == 0 ) + { + nLeadingStringChars = nLeadingStringChars + rInfo.sStrArray[j].getLength(); + } + break; + case NF_SYMBOLTYPE_STAR: + if( bStarFlag ) + { + bRes = lcl_insertStarFillChar( sBuff, k, rInfo.sStrArray[j]); + } + break; + case NF_SYMBOLTYPE_BLANK: + if (rInfo.sStrArray[j].getLength() >= 2) + /*k = */ InsertBlanks(sBuff, k, rInfo.sStrArray[j][1] ); + break; + case NF_SYMBOLTYPE_THSEP: + // #i7284# #102685# Insert separator also if number is divided + // by thousands and the separator is specified somewhere in + // between and not only at the end. + // #i12596# But do not insert if it's a parenthesized negative + // format like (#,) + // In fact, do not insert if divided and regex [0#,],[^0#] and + // no other digit symbol follows (which was already detected + // during scan of format code, otherwise there would be no + // division), else do insert. Same in ImpNumberFill() below. + if ( !bDoThousands && j < NumFor[nIx].GetCount()-1 ) + { + bDoThousands = ((j == 0) || + (rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_DIGIT && + rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_THSEP) || + (rInfo.nTypeArray[j+1] == NF_SYMBOLTYPE_DIGIT)); + } + if ( bDoThousands ) + { + if (k > 0) + { + sBuff.insert(k, rInfo.sStrArray[j]); + } + else if (nDigitCount < nDigCnt) + { + // Leading '#' displays nothing (e.g. no leading + // separator for numbers <1000 with #,##0 format). + // Leading '?' displays blank. + // Everything else, including nothing, displays the + // separator. + sal_Unicode cLeader = 0; + if (j > 0 && rInfo.nTypeArray[j-1] == NF_SYMBOLTYPE_DIGIT) + { + const OUString& rStr = rInfo.sStrArray[j-1]; + sal_Int32 nLen = rStr.getLength(); + if (nLen) + { + cLeader = rStr[ nLen - 1 ]; + } + } + switch (cLeader) + { + case '#': + ; // nothing + break; + case '?': + // replace thousand separator with blank + sBuff.insert(k, ' '); + break; + default: + sBuff.insert(k, rInfo.sStrArray[j]); + } + } + aGrouping.advance(); + } + break; + case NF_SYMBOLTYPE_DIGIT: + { + const OUString& rStr = rInfo.sStrArray[j]; + const sal_Unicode* p1 = rStr.getStr(); + const sal_Unicode* p = p1 + rStr.getLength(); + while ( p1 < p-- ) + { + nDigitCount++; + if (k > 0) + { + k--; + } + else + { + switch (*p) + { + case '0': + sBuff.insert(0, '0'); + break; + case '?': + sBuff.insert(0, ' '); + break; + } + } + if (nDigitCount == nDigCnt && k > 0) + { + // more digits than specified + ImpDigitFill(sBuff, 0, k, nIx, nDigitCount, aGrouping); + } + } + break; + } + case NF_KEY_CCC: // CCC currency + sBuff.insert(k, rScan.GetCurAbbrev()); + break; + case NF_KEY_GENERAL: // "General" in string + { + OUStringBuffer sNum; + ImpGetOutputStandard(rNumber, sNum); + sNum.stripStart('-'); + sBuff.insert(k, sNum.makeStringAndClear()); + break; + } + default: + break; + } // switch + j--; // next format code string + } // while + + k = k + nLeadingStringChars; // MSC converts += to int and then warns, so ... + if (k > nLeadingStringChars) + { + ImpDigitFill(sBuff, nLeadingStringChars, k, nIx, nDigitCount, aGrouping); + } + return bRes; +} + +void SvNumberformat::ImpDigitFill(OUStringBuffer& sStr, // number string + sal_Int32 nStart, // start of digits + sal_Int32 & k, // position within string + sal_uInt16 nIx, // subformat index + sal_Int32 & nDigitCount, // count of integer digits from the right so far + utl::DigitGroupingIterator & rGrouping ) // current grouping +{ + if (NumFor[nIx].Info().bThousand) // Only if grouping fill in separators + { + const OUString& rThousandSep = GetFormatter().GetNumThousandSep(); + while (k > nStart) + { + if (nDigitCount == rGrouping.getPos()) + { + sStr.insert( k, rThousandSep ); + rGrouping.advance(); + } + nDigitCount++; + k--; + } + } + else // simply skip + { + k = nStart; + } +} + +bool SvNumberformat::ImpNumberFill( OUStringBuffer& sBuff, // number string + double& rNumber, // number for "General" format + sal_Int32& k, // position within string + sal_uInt16& j, // symbol index within format code + sal_uInt16 nIx, // subformat index + short eSymbolType, // type of stop condition + bool bInsertRightBlank)// insert blank on right for denominator (default = false) +{ + bool bRes = false; + bool bStop = false; + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + // no normal thousands separators if number divided by thousands + bool bDoThousands = (rInfo.nThousand == 0); + bool bFoundNumber = false; + short nType; + + k = sBuff.getLength(); // behind last digit + + while (!bStop && (nType = rInfo.nTypeArray[j]) != eSymbolType ) // Backwards + { + switch ( nType ) + { + case NF_SYMBOLTYPE_STAR: + if( bStarFlag ) + { + if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP ) + k = 0; // tdf#100842 jump to beginning of number before inserting something else + bRes = lcl_insertStarFillChar( sBuff, k, rInfo.sStrArray[j]); + } + break; + case NF_SYMBOLTYPE_BLANK: + if (rInfo.sStrArray[j].getLength() >= 2) + { + if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP ) + k = 0; // tdf#100842 jump to beginning of number before inserting something else + k = InsertBlanks(sBuff, k, rInfo.sStrArray[j][1] ); + } + break; + case NF_SYMBOLTYPE_THSEP: + // Same as in ImpNumberFillWithThousands() above, do not insert + // if divided and regex [0#,],[^0#] and no other digit symbol + // follows (which was already detected during scan of format + // code, otherwise there would be no division), else do insert. + if ( !bDoThousands && j < NumFor[nIx].GetCount()-1 ) + { + bDoThousands = ((j == 0) || + (rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_DIGIT && + rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_THSEP) || + (rInfo.nTypeArray[j+1] == NF_SYMBOLTYPE_DIGIT)); + } + if ( bDoThousands && k > 0 ) + { + sBuff.insert(k, rInfo.sStrArray[j]); + } + break; + case NF_SYMBOLTYPE_DIGIT: + { + bFoundNumber = true; + sal_uInt16 nPosInsertBlank = bInsertRightBlank ? k : 0; // left alignment of denominator + const OUString& rStr = rInfo.sStrArray[j]; + const sal_Unicode* p1 = rStr.getStr(); + const sal_Unicode* p = p1 + rStr.getLength(); + while ( p1 < p-- ) + { + if (k > 0) + { + k--; + } + else + { + switch (*p) + { + case '0': + sBuff.insert(0, '0'); + break; + case '?': + sBuff.insert(nPosInsertBlank, ' '); + break; + } + } + } + } + break; + case NF_KEY_CCC: // CCC currency + sBuff.insert(k, rScan.GetCurAbbrev()); + break; + case NF_KEY_GENERAL: // Standard in the String + { + OUStringBuffer sNum; + bFoundNumber = true; + ImpGetOutputStandard(rNumber, sNum); + sNum.stripStart('-'); + sBuff.insert(k, sNum.makeStringAndClear()); + } + break; + case NF_SYMBOLTYPE_FRAC_FDIV: // Do Nothing + if (k > 0) + { + k--; + } + break; + + default: + if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP ) + k = 0; // tdf#100842 jump to beginning of number before inserting something else + sBuff.insert(k, rInfo.sStrArray[j]); + break; + } // of switch + if ( j ) + j--; // Next String + else + bStop = true; + } // of while + return bRes; +} + +void SvNumberformat::GetFormatSpecialInfo(bool& bThousand, + bool& IsRed, + sal_uInt16& nPrecision, + sal_uInt16& nLeadingCnt) const +{ + // as before: take info from nNumFor=0 for whole format (for dialog etc.) + + SvNumFormatType nDummyType; + GetNumForInfo( 0, nDummyType, bThousand, nPrecision, nLeadingCnt ); + + // "negative in red" is only useful for the whole format + + const Color* pColor = NumFor[1].GetColor(); + IsRed = fLimit1 == 0.0 && fLimit2 == 0.0 && pColor + && (*pColor == ImpSvNumberformatScan::GetRedColor()); +} + +void SvNumberformat::GetNumForInfo( sal_uInt16 nNumFor, SvNumFormatType& rScannedType, + bool& bThousand, sal_uInt16& nPrecision, sal_uInt16& nLeadingCnt ) const +{ + // take info from a specified sub-format (for XML export) + + if ( nNumFor > 3 ) + { + return; // invalid + } + + const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info(); + rScannedType = rInfo.eScannedType; + bThousand = rInfo.bThousand; + nPrecision = (rInfo.eScannedType == SvNumFormatType::FRACTION) + ? rInfo.nCntExp // number of denominator digits for fraction + : rInfo.nCntPost; + sal_Int32 nPosHash = 1; + if ( rInfo.eScannedType == SvNumFormatType::FRACTION && + ( (nPosHash += GetDenominatorString(nNumFor).indexOf('#')) > 0 ) ) + nPrecision -= nPosHash; + if (bStandard && rInfo.eScannedType == SvNumFormatType::NUMBER) + { + // StandardFormat + nLeadingCnt = 1; + } + else + { + nLeadingCnt = 0; + bool bStop = false; + sal_uInt16 i = 0; + const sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); + while (!bStop && i < nCnt) + { + short nType = rInfo.nTypeArray[i]; + if ( nType == NF_SYMBOLTYPE_DIGIT) + { + const sal_Unicode* p = rInfo.sStrArray[i].getStr(); + while ( *p == '#' ) + { + p++; + } + while ( *p++ == '0' ) + { + nLeadingCnt++; + } + } + else if (nType == NF_SYMBOLTYPE_DECSEP + || nType == NF_SYMBOLTYPE_EXP + || nType == NF_SYMBOLTYPE_FRACBLANK) // Fraction: stop after integer part, + { // do not count '0' of fraction + bStop = true; + } + i++; + } + } +} + +const OUString* SvNumberformat::GetNumForString( sal_uInt16 nNumFor, sal_uInt16 nPos, + bool bString /* = false */ ) const +{ + if ( nNumFor > 3 ) + { + return nullptr; + } + sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); + if ( !nCnt ) + { + return nullptr; + } + if ( nPos == 0xFFFF ) + { + nPos = nCnt - 1; + if ( bString ) + { // Backwards + short const * pType = NumFor[nNumFor].Info().nTypeArray.data() + nPos; + while ( nPos > 0 && (*pType != NF_SYMBOLTYPE_STRING) && + (*pType != NF_SYMBOLTYPE_CURRENCY) ) + { + pType--; + nPos--; + } + if ( (*pType != NF_SYMBOLTYPE_STRING) && (*pType != NF_SYMBOLTYPE_CURRENCY) ) + { + return nullptr; + } + } + } + else if ( nPos > nCnt - 1 ) + { + return nullptr; + } + else if ( bString ) + { + // forward + short const * pType = NumFor[nNumFor].Info().nTypeArray.data() + nPos; + while ( nPos < nCnt && (*pType != NF_SYMBOLTYPE_STRING) && + (*pType != NF_SYMBOLTYPE_CURRENCY) ) + { + pType++; + nPos++; + } + if ( nPos >= nCnt || ((*pType != NF_SYMBOLTYPE_STRING) && + (*pType != NF_SYMBOLTYPE_CURRENCY)) ) + { + return nullptr; + } + } + return &NumFor[nNumFor].Info().sStrArray[nPos]; +} + +short SvNumberformat::GetNumForType( sal_uInt16 nNumFor, sal_uInt16 nPos ) const +{ + if ( nNumFor > 3 ) + { + return 0; + } + sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); + if ( !nCnt ) + { + return 0; + } + if ( nPos == 0xFFFF ) + { + nPos = nCnt - 1; + } + else if ( nPos > nCnt - 1 ) + { + return 0; + } + return NumFor[nNumFor].Info().nTypeArray[nPos]; +} + +bool SvNumberformat::IsNegativeWithoutSign() const +{ + if ( IsSecondSubformatRealNegative() ) + { + const OUString* pStr = GetNumForString( 1, 0, true ); + if ( pStr ) + { + return !HasStringNegativeSign( *pStr ); + } + } + return false; +} + +bool SvNumberformat::IsNegativeInBracket() const +{ + sal_uInt16 nCnt = NumFor[1].GetCount(); + if (!nCnt) + { + return false; + } + auto& tmp = NumFor[1].Info().sStrArray; + return tmp[0] == "(" && tmp[nCnt-1] == ")"; +} + +bool SvNumberformat::HasPositiveBracketPlaceholder() const +{ + sal_uInt16 nCnt = NumFor[0].GetCount(); + return NumFor[0].Info().sStrArray[nCnt-1] == "_)"; +} + +DateOrder SvNumberformat::GetDateOrder() const +{ + if ( eType & SvNumFormatType::DATE ) + { + auto& rTypeArray = NumFor[0].Info().nTypeArray; + sal_uInt16 nCnt = NumFor[0].GetCount(); + for ( sal_uInt16 j=0; j 3 ) + { + return nullptr; + } + return NumFor[nNumFor].GetColor(); +} + +static void lcl_SvNumberformat_AddLimitStringImpl( OUString& rStr, + SvNumberformatLimitOps eOp, + double fLimit, std::u16string_view rDecSep ) +{ + if ( eOp == NUMBERFORMAT_OP_NO ) + return; + + switch ( eOp ) + { + case NUMBERFORMAT_OP_EQ : + rStr = "[="; + break; + case NUMBERFORMAT_OP_NE : + rStr = "[<>"; + break; + case NUMBERFORMAT_OP_LT : + rStr = "[<"; + break; + case NUMBERFORMAT_OP_LE : + rStr = "[<="; + break; + case NUMBERFORMAT_OP_GT : + rStr = "[>"; + break; + case NUMBERFORMAT_OP_GE : + rStr = "[>="; + break; + default: + SAL_WARN( "svl.numbers", "unsupported number format" ); + break; + } + rStr += ::rtl::math::doubleToUString( fLimit, + rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, + rDecSep[0], true); + rStr += "]"; +} + +static void lcl_insertLCID( OUStringBuffer& rFormatStr, sal_uInt32 nLCID, sal_Int32 nPosInsertLCID, bool bDBNumInserted ) +{ + if ( nLCID == 0 ) + return; + if (nPosInsertLCID == rFormatStr.getLength() && !bDBNumInserted) + // No format code, no locale. + return; + + OUStringBuffer aLCIDString = OUString::number( nLCID , 16 ).toAsciiUpperCase(); + // Search for only last DBNum which is the last element before insertion position + if ( bDBNumInserted && nPosInsertLCID >= 8 + && aLCIDString.getLength() > 4 + && rFormatStr.indexOf( "[DBNum", nPosInsertLCID-8) == nPosInsertLCID-8 ) + { // remove DBNumX code if long LCID + nPosInsertLCID -= 8; + rFormatStr.remove( nPosInsertLCID, 8 ); + } + aLCIDString.insert( 0, "[$-" ); + aLCIDString.append( "]" ); + rFormatStr.insert( nPosInsertLCID, aLCIDString.makeStringAndClear() ); +} + +/** Increment nAlphabetID for CJK numerals + * +1 for financial numerals [NatNum2] + * +2 for Arabic fullwidth numerals [NatNum3] + * */ +static void lcl_incrementAlphabetWithNatNum ( sal_uInt32& nAlphabetID, sal_uInt32 nNatNum ) +{ + if ( nNatNum == 2) // financial + nAlphabetID += 1; + else if ( nNatNum == 3) + nAlphabetID += 2; + nAlphabetID = nAlphabetID << 24; +} + +OUString SvNumberformat::GetMappedFormatstring( const NfKeywordTable& rKeywords, + const LocaleDataWrapper& rLocWrp, + LanguageType nOriginalLang /* =LANGUAGE_DONTKNOW */, + bool bSystemLanguage /* =false */ ) const +{ + OUStringBuffer aStr; + if (maLocale.meSubstitute != LocaleType::Substitute::NONE) + { + // XXX: theoretically this could clash with the first subformat's + // lcl_insertLCID() below, in practice as long as it is used for system + // time and date modifiers it shouldn't (i.e. there is no calendar or + // numeral specified as well). + aStr.append("[$-" + maLocale.generateCode() + "]"); + } + bool bDefault[4]; + // 1 subformat matches all if no condition specified, + bDefault[0] = ( NumFor[1].GetCount() == 0 && eOp1 == NUMBERFORMAT_OP_NO ); + // with 2 subformats [>=0];[<0] is implied if no condition specified + bDefault[1] = ( !bDefault[0] && NumFor[2].GetCount() == 0 && + eOp1 == NUMBERFORMAT_OP_GE && fLimit1 == 0.0 && + eOp2 == NUMBERFORMAT_OP_NO && fLimit2 == 0.0 ); + // with 3 or more subformats [>0];[<0];[=0] is implied if no condition specified, + // note that subformats may be empty (;;;) and NumFor[2].GetCount()>0 is not checked. + bDefault[2] = ( !bDefault[0] && !bDefault[1] && + eOp1 == NUMBERFORMAT_OP_GT && fLimit1 == 0.0 && + eOp2 == NUMBERFORMAT_OP_LT && fLimit2 == 0.0 ); + bool bDefaults = bDefault[0] || bDefault[1] || bDefault[2]; + // from now on bDefault[] values are used to append empty subformats at the end + bDefault[3] = false; + if ( !bDefaults ) + { + // conditions specified + if ( eOp1 != NUMBERFORMAT_OP_NO && eOp2 == NUMBERFORMAT_OP_NO ) + { + bDefault[0] = bDefault[1] = true; // [];x + } + else if ( eOp1 != NUMBERFORMAT_OP_NO && eOp2 != NUMBERFORMAT_OP_NO && + NumFor[2].GetCount() == 0 ) + { + bDefault[0] = bDefault[1] = bDefault[2] = bDefault[3] = true; // [];[];; + } + // nothing to do if conditions specified for every subformat + } + else if ( bDefault[0] ) + { + bDefault[0] = false; // a single unconditional subformat is never delimited + } + else + { + if ( bDefault[2] && NumFor[2].GetCount() == 0 && NumFor[1].GetCount() > 0 ) + { + bDefault[3] = true; // special cases x;x;; and ;x;; + } + for ( int i=0; i<3 && !bDefault[i]; ++i ) + { + bDefault[i] = true; + } + } + int nSem = 0; // needed ';' delimiters + int nSub = 0; // subformats delimited so far + for ( int n=0; n<4; n++ ) + { + if ( n > 0 && NumFor[n].Info().eScannedType != SvNumFormatType::UNDEFINED ) + { + nSem++; + } + OUString aPrefix; + + if ( !bDefaults ) + { + switch ( n ) + { + case 0 : + lcl_SvNumberformat_AddLimitStringImpl( aPrefix, eOp1, + fLimit1, rLocWrp.getNumDecimalSep() ); + break; + case 1 : + lcl_SvNumberformat_AddLimitStringImpl( aPrefix, eOp2, + fLimit2, rLocWrp.getNumDecimalSep() ); + break; + } + } + + const OUString& rColorName = NumFor[n].GetColorName(); + if ( !rColorName.isEmpty() ) + { + const NfKeywordTable & rKey = rScan.GetKeywords(); + for ( int j = NF_KEY_FIRSTCOLOR; j <= NF_KEY_LASTCOLOR; j++ ) + { + if ( rKey[j] == rColorName ) + { + aPrefix += "[" + rKeywords[j] + "]"; + break; // for + } + } + } + + SvNumberNatNum aNatNum = NumFor[n].GetNatNum(); + bool bDBNumInserted = false; + if (aNatNum.IsComplete() && (aNatNum.GetDBNum() > 0 || nOriginalLang != LANGUAGE_DONTKNOW)) + { // GetFormatStringForExcel() may have changed language to en_US + if (aNatNum.GetLang() == LANGUAGE_ENGLISH_US && nOriginalLang != LANGUAGE_DONTKNOW) + aNatNum.SetLang( nOriginalLang ); + if ( aNatNum.GetDBNum() > 0 ) + { + aPrefix += "[DBNum" + OUString::number( aNatNum.GetDBNum() ) + "]"; + bDBNumInserted = true; + } + } + + sal_uInt16 nCnt = NumFor[n].GetCount(); + if ( nSem && (nCnt || !aPrefix.isEmpty()) ) + { + for ( ; nSem; --nSem ) + { + aStr.append( ';' ); + } + for ( ; nSub <= n; ++nSub ) + { + bDefault[nSub] = false; + } + } + + if ( !aPrefix.isEmpty() ) + { + aStr.append( aPrefix ); + } + sal_Int32 nPosHaveLCID = -1; + sal_Int32 nPosInsertLCID = aStr.getLength(); + sal_uInt32 nCalendarID = 0x0000000; // Excel ID of calendar used in sub-format see tdf#36038 + constexpr sal_uInt32 kCalGengou = 0x0030000; + if ( nCnt ) + { + auto& rTypeArray = NumFor[n].Info().nTypeArray; + auto& rStrArray = NumFor[n].Info().sStrArray; + for ( sal_uInt16 j=0; j 1 && rStrArray[j][1] == '+' ) + aStr.append( "+" ); + else + // tdf#102370: Excel code for exponent without sign + aStr.append( "-" ); + break; + case NF_SYMBOLTYPE_DATESEP : + aStr.append( rLocWrp.getDateSep() ); + break; + case NF_SYMBOLTYPE_TIMESEP : + aStr.append( rLocWrp.getTimeSep() ); + break; + case NF_SYMBOLTYPE_TIME100SECSEP : + aStr.append( rLocWrp.getTime100SecSep() ); + break; + case NF_SYMBOLTYPE_FRACBLANK : + case NF_SYMBOLTYPE_STRING : + if ( rStrArray[j].getLength() == 1 ) + { + if ( rTypeArray[j] == NF_SYMBOLTYPE_STRING ) + aStr.append( '\\' ); + aStr.append( rStrArray[j] ); + } + else + { + aStr.append( '"' ); + aStr.append( rStrArray[j] ); + aStr.append( '"' ); + } + break; + case NF_SYMBOLTYPE_CALDEL : + if (j + 1 >= nCnt) + break; + if ( rStrArray[j+1] == "gengou" ) + { + nCalendarID = kCalGengou; + } + else if ( rStrArray[j+1] == "hijri" ) + { + nCalendarID = 0x0060000; + } + else if ( rStrArray[j+1] == "buddhist" ) + { + nCalendarID = 0x0070000; + } + else if ( rStrArray[j+1] == "jewish" ) + { + nCalendarID = 0x0080000; + } + // Other calendars (see tdf#36038) not corresponding between LibO and XL. + // However, skip any calendar modifier and don't write + // as format code (if not as literal string). + j += 2; + break; + case NF_SYMBOLTYPE_CURREXT : + nPosHaveLCID = aStr.getLength(); + aStr.append( rStrArray[j] ); + break; + default: + aStr.append( rStrArray[j] ); + } + } + } + } + sal_uInt32 nAlphabetID = 0x0000000; // Excel ID of alphabet used for numerals see tdf#36038 + LanguageType nLanguageID = LANGUAGE_SYSTEM; + if ( aNatNum.IsComplete() ) + { + nLanguageID = MsLangId::getRealLanguage( aNatNum.GetLang()); + if ( aNatNum.GetNatNum() == 0 ) + { + nAlphabetID = 0x01000000; // Arabic-european numerals + } + else if ( nCalendarID > 0 || aNatNum.GetDBNum() == 0 || aNatNum.GetDBNum() == aNatNum.GetNatNum() ) + { // if no DBNum code then use long LCID + // if DBNum value != NatNum value, use DBNum and not extended LCID + // if calendar, then DBNum will be removed + LanguageType pri = primary(nLanguageID); + if ( pri == LANGUAGE_ARABIC_PRIMARY_ONLY ) + nAlphabetID = 0x02000000; // Arabic-indic numerals + else if ( pri == primary(LANGUAGE_FARSI) ) + nAlphabetID = 0x03000000; // Farsi numerals + else if ( pri.anyOf( + primary(LANGUAGE_HINDI), + primary(LANGUAGE_MARATHI), + primary(LANGUAGE_NEPALI) )) + nAlphabetID = 0x04000000; // Devanagari numerals + else if ( pri == primary(LANGUAGE_BENGALI) ) + nAlphabetID = 0x05000000; // Bengali numerals + else if ( pri == primary(LANGUAGE_PUNJABI) ) + { + if ( nLanguageID == LANGUAGE_PUNJABI_ARABIC_LSO ) + nAlphabetID = 0x02000000; // Arabic-indic numerals + else + nAlphabetID = 0x06000000; // Punjabi numerals + } + else if ( pri == primary(LANGUAGE_GUJARATI) ) + nAlphabetID = 0x07000000; // Gujarati numerals + else if ( pri == primary(LANGUAGE_ODIA)) + nAlphabetID = 0x08000000; // Odia (Oriya) numerals + else if ( pri == primary(LANGUAGE_TAMIL)) + nAlphabetID = 0x09000000; // Tamil numerals + else if ( pri == primary(LANGUAGE_TELUGU)) + nAlphabetID = 0x0A000000; // Telugu numerals + else if ( pri == primary(LANGUAGE_KANNADA)) + nAlphabetID = 0x0B000000; // Kannada numerals + else if ( pri == primary(LANGUAGE_MALAYALAM)) + nAlphabetID = 0x0C000000; // Malayalam numerals + else if ( pri == primary(LANGUAGE_THAI)) + { + // The Thai T NatNum modifier during Xcl export. + if ( rKeywords[NF_KEY_THAI_T] == "T" ) + nAlphabetID = 0x0D000000; // Thai numerals + } + else if ( pri == primary(LANGUAGE_LAO)) + nAlphabetID = 0x0E000000; // Lao numerals + else if ( pri == primary(LANGUAGE_TIBETAN)) + nAlphabetID = 0x0F000000; // Tibetan numerals + else if ( pri == primary(LANGUAGE_BURMESE)) + nAlphabetID = 0x10000000; // Burmese numerals + else if ( pri == primary(LANGUAGE_TIGRIGNA_ETHIOPIA)) + nAlphabetID = 0x11000000; // Tigrigna numerals + else if ( pri == primary(LANGUAGE_KHMER)) + nAlphabetID = 0x12000000; // Khmer numerals + else if ( pri == primary(LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA)) + { + if ( nLanguageID != LANGUAGE_MONGOLIAN_CYRILLIC_MONGOLIA + && nLanguageID != LANGUAGE_MONGOLIAN_CYRILLIC_LSO ) + nAlphabetID = 0x13000000; // Mongolian numerals + } + // CJK numerals + else if ( pri == primary(LANGUAGE_JAPANESE)) + { + nAlphabetID = 0x1B; + lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() ); + } + else if ( pri == primary(LANGUAGE_CHINESE)) + { + if ( nLanguageID == LANGUAGE_CHINESE_TRADITIONAL + || nLanguageID == LANGUAGE_CHINESE_HONGKONG + || nLanguageID == LANGUAGE_CHINESE_MACAU ) + { + nAlphabetID = 0x21; + lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() ); + } + else // LANGUAGE_CHINESE_SIMPLIFIED + { + nAlphabetID = 0x1E; + lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() ); + } + } + else if ( pri == primary(LANGUAGE_KOREAN)) + { + if ( aNatNum.GetNatNum() == 9 ) // Hangul + { + nAlphabetID = 0x27000000; + } + else + { + nAlphabetID = 0x24; + lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() ); + } + } + } + // Add LCID to DBNum + if ( aNatNum.GetDBNum() > 0 && nLanguageID == LANGUAGE_SYSTEM ) + nLanguageID = MsLangId::getRealLanguage( aNatNum.GetLang()); + } + else if (nPosHaveLCID < 0) + { + // Do not insert a duplicated LCID that was already given with a + // currency format as [$R-1C09] + if (!bSystemLanguage && nOriginalLang != LANGUAGE_DONTKNOW) + { + // Explicit locale, write only to the first subformat. + if (n == 0) + nLanguageID = MsLangId::getRealLanguage( nOriginalLang); + } + else if (bSystemLanguage && maLocale.meLanguageWithoutLocaleData != LANGUAGE_DONTKNOW) + { + // Explicit locale but no locale data thus assigned to system + // locale, preserve for roundtrip, write only to the first + // subformat. + if (n == 0) + nLanguageID = maLocale.meLanguageWithoutLocaleData; + } + } + if ( nCalendarID > 0 ) + { // Add alphabet and language to calendar + if ( nAlphabetID == 0 ) + nAlphabetID = 0x01000000; + if ( nLanguageID == LANGUAGE_SYSTEM && nOriginalLang != LANGUAGE_DONTKNOW ) + nLanguageID = nOriginalLang; + } + lcl_insertLCID( aStr, nAlphabetID + nCalendarID + static_cast(nLanguageID), nPosInsertLCID, + bDBNumInserted); + } + for ( ; nSub<4 && bDefault[nSub]; ++nSub ) + { // append empty subformats + aStr.append( ';' ); + } + return aStr.makeStringAndClear(); +} + +OUString SvNumberformat::ImpGetNatNumString( const SvNumberNatNum& rNum, + sal_Int64 nVal, sal_uInt16 nMinDigits ) const +{ + OUString aStr; + if ( nMinDigits ) + { + if ( nMinDigits == 2 ) + { + // speed up the most common case + if ( 0 <= nVal && nVal < 10 ) + { + sal_Unicode aBuf[2]; + aBuf[0] = '0'; + aBuf[1] = '0' + nVal; + aStr = OUString(aBuf, SAL_N_ELEMENTS(aBuf)); + } + else + { + aStr = OUString::number( nVal ); + } + } + else + { + OUString aValStr( OUString::number( nVal ) ); + if ( aValStr.getLength() >= nMinDigits ) + { + aStr = aValStr; + } + else + { + OUStringBuffer aBuf; + for(sal_Int32 index = 0; index < nMinDigits - aValStr.getLength(); ++index) + { + aBuf.append('0'); + } + aBuf.append(aValStr); + aStr = aBuf.makeStringAndClear(); + } + } + } + else + { + aStr = OUString::number( nVal ); + } + return impTransliterate(aStr, rNum); +} + +OUString SvNumberformat::impTransliterateImpl(const OUString& rStr, + const SvNumberNatNum& rNum ) const +{ + css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() ); + return GetFormatter().GetNatNum()->getNativeNumberStringParams(rStr, aLocale, rNum.GetNatNum(), + rNum.GetParams()); +} + +void SvNumberformat::impTransliterateImpl(OUStringBuffer& rStr, + const SvNumberNatNum& rNum ) const +{ + css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() ); + + rStr = GetFormatter().GetNatNum()->getNativeNumberStringParams( + OUString::unacquired(rStr), aLocale, rNum.GetNatNum(), rNum.GetParams()); +} + +OUString SvNumberformat::impTransliterateImpl(const OUString& rStr, + const SvNumberNatNum& rNum, + const sal_uInt16 nDateKey) const +{ + // no KEYWORD=argument list in NatNum12 + if (rNum.GetParams().indexOf('=') == -1) + return impTransliterateImpl( rStr, rNum); + + const NfKeywordTable & rKeywords = rScan.GetKeywords(); + + // Format: KEYWORD=numbertext_prefix, ..., for example: + // [NatNum12 YYYY=title ordinal,MMMM=article, D=ordinal-number] + sal_Int32 nField = -1; + do + { + nField = rNum.GetParams().indexOf(OUStringConcatenation(rKeywords[nDateKey] + "="), ++nField); + } + while (nField != -1 && nField != 0 && + (rNum.GetParams()[nField - 1] != ',' && + rNum.GetParams()[nField - 1] != ' ')); + + // no format specified for actual keyword + if (nField == -1) + return rStr; + + sal_Int32 nKeywordLen = rKeywords[nDateKey].getLength() + 1; + sal_Int32 nFieldEnd = rNum.GetParams().indexOf(',', nField); + + if (nFieldEnd == -1) + nFieldEnd = rNum.GetParams().getLength(); + + css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() ); + + return GetFormatter().GetNatNum()->getNativeNumberStringParams( + rStr, aLocale, rNum.GetNatNum(), + rNum.GetParams().copy(nField + nKeywordLen, nFieldEnd - nField - nKeywordLen)); +} + +void SvNumberformat::GetNatNumXml( css::i18n::NativeNumberXmlAttributes2& rAttr, + sal_uInt16 nNumFor ) const +{ + if ( nNumFor <= 3 ) + { + const SvNumberNatNum& rNum = NumFor[nNumFor].GetNatNum(); + if ( rNum.IsSet() ) + { + css::lang::Locale aLocale( + LanguageTag( rNum.GetLang() ).getLocale() ); + + /* TODO: a new XNativeNumberSupplier2::convertToXmlAttributes() + * should rather return NativeNumberXmlAttributes2 and places + * adapted, and whether to fill Spellout or something different + * should be internal there. */ + css::i18n::NativeNumberXmlAttributes aTmp( + GetFormatter().GetNatNum()->convertToXmlAttributes( + aLocale, rNum.GetNatNum())); + rAttr.Locale = aTmp.Locale; + rAttr.Format = aTmp.Format; + rAttr.Style = aTmp.Style; + if ( NatNumTakesParameters(rNum.GetNatNum()) ) + { + // NatNum12 spell out numbers, dates and money amounts + rAttr.Spellout = rNum.GetParams(); + // Mutually exclusive. + rAttr.Format.clear(); + rAttr.Style.clear(); + } + else + { + rAttr.Spellout.clear(); + } + } + else + { + rAttr = css::i18n::NativeNumberXmlAttributes2(); + } + } + else + { + rAttr = css::i18n::NativeNumberXmlAttributes2(); + } +} + +// static +bool SvNumberformat::HasStringNegativeSign( const OUString& rStr ) +{ + // For Sign '-' needs to be at the start or at the end of the string (blanks ignored) + sal_Int32 nLen = rStr.getLength(); + if ( !nLen ) + { + return false; + } + const sal_Unicode* const pBeg = rStr.getStr(); + const sal_Unicode* const pEnd = pBeg + nLen; + const sal_Unicode* p = pBeg; + do + { // Start + if ( *p == '-' ) + { + return true; + } + } + while ( *p == ' ' && ++p < pEnd ); + + p = pEnd - 1; + + do + { // End + if ( *p == '-' ) + { + return true; + } + } + while ( *p == ' ' && pBeg < --p ); + return false; +} + +// static +bool SvNumberformat::IsInQuote( const OUString& rStr, sal_Int32 nPos, + sal_Unicode cQuote, sal_Unicode cEscIn, sal_Unicode cEscOut ) +{ + sal_Int32 nLen = rStr.getLength(); + if ( nPos >= nLen ) + { + return false; + } + const sal_Unicode* p0 = rStr.getStr(); + const sal_Unicode* p = p0; + const sal_Unicode* p1 = p0 + nPos; + bool bQuoted = false; + while ( p <= p1 ) + { + if ( *p == cQuote ) + { + if ( p == p0 ) + { + bQuoted = true; + } + else if ( bQuoted ) + { + if ( *(p-1) != cEscIn ) + { + bQuoted = false; + } + } + else + { + if ( *(p-1) != cEscOut ) + { + bQuoted = true; + } + } + } + p++; + } + return bQuoted; +} + +// static +sal_Int32 SvNumberformat::GetQuoteEnd( const OUString& rStr, sal_Int32 nPos, + sal_Unicode cQuote, sal_Unicode cEscIn ) +{ + if ( nPos < 0 ) + { + return -1; + } + sal_Int32 nLen = rStr.getLength(); + if ( nPos >= nLen ) + { + return -1; + } + if ( !IsInQuote( rStr, nPos, cQuote, cEscIn ) ) + { + if ( rStr[ nPos ] == cQuote ) + { + return nPos; // Closing cQuote + } + return -1; + } + const sal_Unicode* p0 = rStr.getStr(); + const sal_Unicode* p = p0 + nPos; + const sal_Unicode* p1 = p0 + nLen; + while ( p < p1 ) + { + if ( *p == cQuote && p > p0 && *(p-1) != cEscIn ) + { + return sal::static_int_cast< sal_Int32 >(p - p0); + } + p++; + } + return nLen; // End of String +} + +sal_uInt16 SvNumberformat::GetNumForNumberElementCount( sal_uInt16 nNumFor ) const +{ + if ( nNumFor < 4 ) + { + sal_uInt16 nCnt = NumFor[nNumFor].GetCount(); + return nCnt - ImpGetNumForStringElementCount( nNumFor ); + } + return 0; +} + +sal_uInt16 SvNumberformat::ImpGetNumForStringElementCount( sal_uInt16 nNumFor ) const +{ + sal_uInt16 nCnt = 0; + sal_uInt16 nNumForCnt = NumFor[nNumFor].GetCount(); + auto& rTypeArray = NumFor[nNumFor].Info().nTypeArray; + for ( sal_uInt16 j=0; j 0 && rTypeArray[j-1] == NF_SYMBOLTYPE_DEL && j < nNumForCnt-1 ) + { + j++; + sString.append( NumFor[0].Info().sStrArray[j] ); + } + if (nPrecision > 0) + { + sString.append( rLoc().getTime100SecSep() ); + padToLength(sString, sString.getLength() + nPrecision, '0'); + } + break; + case NF_SYMBOLTYPE_TIME100SECSEP: + case NF_SYMBOLTYPE_DIGIT: + break; + case NF_SYMBOLTYPE_STRING: + sString.append( "\"" ); + [[fallthrough]]; + default: + sString.append( NumFor[0].Info().sStrArray[j] ); + if (rTypeArray[j] == NF_SYMBOLTYPE_STRING) + { + sString.append( "\"" ); + } + } + } + + return sString.makeStringAndClear(); +} + +sal_uInt16 SvNumberformat::GetThousandDivisorPrecision( sal_uInt16 nIx ) const +{ + if (nIx >= 4) + return 0; + + const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info(); + + if (rInfo.eScannedType != SvNumFormatType::NUMBER && rInfo.eScannedType != SvNumFormatType::CURRENCY) + return 0; + + if (rInfo.nThousand == FLAG_STANDARD_IN_FORMAT) + return SvNumberFormatter::UNLIMITED_PRECISION; + + return rInfo.nThousand * 3; +} + +const CharClass& SvNumberformat::rChrCls() const +{ + return rScan.GetChrCls(); +} + +const LocaleDataWrapper& SvNumberformat::rLoc() const +{ + return rScan.GetLoc(); +} + +CalendarWrapper& SvNumberformat::GetCal() const +{ + return rScan.GetCal(); +} + +const SvNumberFormatter& SvNumberformat::GetFormatter() const +{ + return *rScan.GetNumberformatter(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/numbers/zforscan.cxx b/svl/source/numbers/zforscan.cxx new file mode 100644 index 000000000..a5f256376 --- /dev/null +++ b/svl/source/numbers/zforscan.cxx @@ -0,0 +1,3322 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "zforscan.hxx" + +#include +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 ImpSvNumberformatScan::StandardColor{ + COL_BLACK, COL_LIGHTBLUE, COL_LIGHTGREEN, COL_LIGHTCYAN, COL_LIGHTRED, + COL_LIGHTMAGENTA, COL_BROWN, COL_GRAY, COL_YELLOW, COL_WHITE +}; + +// This vector will hold *only* the color names in German language. +static const std::u16string_view& GermanColorName(size_t i) +{ + static const std::u16string_view sGermanColorNames[]{ u"FARBE", u"SCHWARZ", u"BLAU", + u"GRÜN", u"CYAN", u"ROT", + u"MAGENTA", u"BRAUN", u"GRAU", + u"GELB", u"WEISS" }; + assert(i < SAL_N_ELEMENTS(sGermanColorNames)); + return sGermanColorNames[i]; +} + +ImpSvNumberformatScan::ImpSvNumberformatScan( SvNumberFormatter* pFormatterP ) + : maNullDate( 30, 12, 1899) + , eNewLnge(LANGUAGE_DONTKNOW) + , eTmpLnge(LANGUAGE_DONTKNOW) + , nCurrPos(-1) + , meKeywordLocalization(KeywordLocalization::AllowEnglish) +{ + pFormatter = pFormatterP; + xNFC = css::i18n::NumberFormatMapper::create( pFormatter->GetComponentContext() ); + bConvertMode = false; + mbConvertDateOrder = false; + bConvertSystemToSystem = false; + bKeywordsNeedInit = true; // locale dependent and not locale dependent keywords + bCompatCurNeedInit = true; // locale dependent compatibility currency strings + + static_assert( NF_KEY_BLACK - NF_KEY_COLOR == 1, "bad FARBE(COLOR), SCHWARZ(BLACK) sequence"); + static_assert( NF_KEY_FIRSTCOLOR - NF_KEY_COLOR == 1, "bad color sequence"); + static_assert( NF_MAX_DEFAULT_COLORS + 1 == 11, "bad color count"); + static_assert( NF_KEY_WHITE - NF_KEY_COLOR + 1 == 11, "bad color sequence count"); + + nStandardPrec = 2; + + Reset(); +} + +ImpSvNumberformatScan::~ImpSvNumberformatScan() +{ + Reset(); +} + +void ImpSvNumberformatScan::ChangeIntl( KeywordLocalization eKeywordLocalization ) +{ + meKeywordLocalization = eKeywordLocalization; + bKeywordsNeedInit = true; + bCompatCurNeedInit = true; + // may be initialized by InitSpecialKeyword() + sKeyword[NF_KEY_TRUE].clear(); + sKeyword[NF_KEY_FALSE].clear(); +} + +void ImpSvNumberformatScan::InitSpecialKeyword( NfKeywordIndex eIdx ) const +{ + switch ( eIdx ) + { + case NF_KEY_TRUE : + const_cast(this)->sKeyword[NF_KEY_TRUE] = + pFormatter->GetCharClass()->uppercase( pFormatter->GetLocaleData()->getTrueWord() ); + if ( sKeyword[NF_KEY_TRUE].isEmpty() ) + { + SAL_WARN( "svl.numbers", "InitSpecialKeyword: TRUE_WORD?" ); + const_cast(this)->sKeyword[NF_KEY_TRUE] = sEnglishKeyword[NF_KEY_TRUE]; + } + break; + case NF_KEY_FALSE : + const_cast(this)->sKeyword[NF_KEY_FALSE] = + pFormatter->GetCharClass()->uppercase( pFormatter->GetLocaleData()->getFalseWord() ); + if ( sKeyword[NF_KEY_FALSE].isEmpty() ) + { + SAL_WARN( "svl.numbers", "InitSpecialKeyword: FALSE_WORD?" ); + const_cast(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(this); + // currency symbol for old style ("automatic") compatibility format codes + pFormatter->GetCompatibilityCurrency( pThis->sCurSymbol, pThis->sCurAbbrev ); + // currency symbol upper case + pThis->sCurString = pFormatter->GetCharClass()->uppercase( sCurSymbol ); + bCompatCurNeedInit = false; +} + +void ImpSvNumberformatScan::InitKeywords() const +{ + if ( !bKeywordsNeedInit ) + return ; + const_cast(this)->SetDependentKeywords(); + bKeywordsNeedInit = false; +} + +/** Extract the name of General, Standard, Whatever, ignoring leading modifiers + such as [NatNum1]. */ +static OUString lcl_extractStandardGeneralName( const OUString & rCode ) +{ + OUString aStr; + const sal_Unicode* p = rCode.getStr(); + const sal_Unicode* const pStop = p + rCode.getLength(); + const sal_Unicode* pBeg = p; // name begins here + bool bMod = false; + bool bDone = false; + while (p < pStop && !bDone) + { + switch (*p) + { + case '[': + bMod = true; + break; + case ']': + if (bMod) + { + bMod = false; + pBeg = p+1; + } + // else: would be a locale data error, easily to be spotted in + // UI dialog + break; + case ';': + if (!bMod) + { + bDone = true; + --p; // put back, increment by one follows + } + break; + } + ++p; + if (bMod) + { + pBeg = p; + } + } + if (pBeg < p) + { + aStr = rCode.copy( pBeg - rCode.getStr(), p - pBeg); + } + return aStr; +} + +void ImpSvNumberformatScan::SetDependentKeywords() +{ + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + + const CharClass* pCharClass = pFormatter->GetCharClass(); + const LocaleDataWrapper* pLocaleData = pFormatter->GetLocaleData(); + // #80023# be sure to generate keywords for the loaded Locale, not for the + // requested Locale, otherwise number format codes might not match + const LanguageTag& rLoadedLocale = pLocaleData->getLoadedLanguageTag(); + LanguageType eLang = rLoadedLocale.getLanguageType( false); + + bool bL10n = (meKeywordLocalization != KeywordLocalization::EnglishOnly); + if (bL10n) + { + // Check if this actually is a locale that uses any localized keywords, + // if not then disable localized keywords completely. + if ( !eLang.anyOf( LANGUAGE_GERMAN, + LANGUAGE_GERMAN_SWISS, + LANGUAGE_GERMAN_AUSTRIAN, + LANGUAGE_GERMAN_LUXEMBOURG, + LANGUAGE_GERMAN_LIECHTENSTEIN, + LANGUAGE_DUTCH, + LANGUAGE_DUTCH_BELGIAN, + LANGUAGE_FRENCH, + LANGUAGE_FRENCH_BELGIAN, + LANGUAGE_FRENCH_CANADIAN, + LANGUAGE_FRENCH_SWISS, + LANGUAGE_FRENCH_LUXEMBOURG, + LANGUAGE_FRENCH_MONACO, + LANGUAGE_FINNISH, + LANGUAGE_ITALIAN, + LANGUAGE_ITALIAN_SWISS, + LANGUAGE_DANISH, + LANGUAGE_NORWEGIAN, + LANGUAGE_NORWEGIAN_BOKMAL, + LANGUAGE_NORWEGIAN_NYNORSK, + LANGUAGE_SWEDISH, + LANGUAGE_SWEDISH_FINLAND, + LANGUAGE_PORTUGUESE, + LANGUAGE_PORTUGUESE_BRAZILIAN, + LANGUAGE_SPANISH_MODERN, + LANGUAGE_SPANISH_DATED, + LANGUAGE_SPANISH_MEXICAN, + LANGUAGE_SPANISH_GUATEMALA, + LANGUAGE_SPANISH_COSTARICA, + LANGUAGE_SPANISH_PANAMA, + LANGUAGE_SPANISH_DOMINICAN_REPUBLIC, + LANGUAGE_SPANISH_VENEZUELA, + LANGUAGE_SPANISH_COLOMBIA, + LANGUAGE_SPANISH_PERU, + LANGUAGE_SPANISH_ARGENTINA, + LANGUAGE_SPANISH_ECUADOR, + LANGUAGE_SPANISH_CHILE, + LANGUAGE_SPANISH_URUGUAY, + LANGUAGE_SPANISH_PARAGUAY, + LANGUAGE_SPANISH_BOLIVIA, + LANGUAGE_SPANISH_EL_SALVADOR, + LANGUAGE_SPANISH_HONDURAS, + LANGUAGE_SPANISH_NICARAGUA, + LANGUAGE_SPANISH_PUERTO_RICO )) + { + bL10n = false; + meKeywordLocalization = KeywordLocalization::EnglishOnly; + } + } + + // Init the current NfKeywordTable with English keywords. + sKeyword = sEnglishKeyword; + + // Set the uppercase localized General name, e.g. Standard -> STANDARD + i18n::NumberFormatCode aFormat = xNFC->getFormatCode( NF_NUMBER_STANDARD, rLoadedLocale.getLocale() ); + sNameStandardFormat = lcl_extractStandardGeneralName( aFormat.Code ); + sKeyword[NF_KEY_GENERAL] = pCharClass->uppercase( sNameStandardFormat ); + + // Thai T NatNum special. Other locale's small letter 't' results in upper + // case comparison not matching but length does in conversion mode. Ugly. + if (eLang == LANGUAGE_THAI) + { + sKeyword[NF_KEY_THAI_T] = "T"; + } + else + { + sKeyword[NF_KEY_THAI_T] = sEnglishKeyword[NF_KEY_THAI_T]; + } + + // boolean keywords + InitSpecialKeyword( NF_KEY_TRUE ); + InitSpecialKeyword( NF_KEY_FALSE ); + + // Boolean equivalent format codes that are written to Excel files, may + // have been written to ODF as well, specifically if such loaded Excel file + // was saved as ODF, and shall result in proper Boolean again. + // "TRUE";"TRUE";"FALSE" + sBooleanEquivalent1 = "\"" + sKeyword[NF_KEY_TRUE] + "\";\"" + + sKeyword[NF_KEY_TRUE] + "\";\"" + sKeyword[NF_KEY_FALSE] + "\""; + // [>0]"TRUE";[<0]"TRUE";"FALSE" + sBooleanEquivalent2 = "[>0]\"" + sKeyword[NF_KEY_TRUE] + "\";[<0]\"" + + sKeyword[NF_KEY_TRUE] + "\";\"" + sKeyword[NF_KEY_FALSE] + "\""; + + // compatibility currency strings + InitCompatCur(); + + if (!bL10n) + return; + + // All locale dependent keywords overrides follow. + + if ( eLang.anyOf( + LANGUAGE_GERMAN, + LANGUAGE_GERMAN_SWISS, + LANGUAGE_GERMAN_AUSTRIAN, + LANGUAGE_GERMAN_LUXEMBOURG, + LANGUAGE_GERMAN_LIECHTENSTEIN)) + { + //! all capital letters + sKeyword[NF_KEY_M] = "M"; // month 1 + sKeyword[NF_KEY_MM] = "MM"; // month 01 + sKeyword[NF_KEY_MMM] = "MMM"; // month Jan + sKeyword[NF_KEY_MMMM] = "MMMM"; // month Januar + sKeyword[NF_KEY_MMMMM] = "MMMMM"; // month J + sKeyword[NF_KEY_H] = "H"; // hour 2 + sKeyword[NF_KEY_HH] = "HH"; // hour 02 + sKeyword[NF_KEY_D] = "T"; + sKeyword[NF_KEY_DD] = "TT"; + sKeyword[NF_KEY_DDD] = "TTT"; + sKeyword[NF_KEY_DDDD] = "TTTT"; + sKeyword[NF_KEY_YY] = "JJ"; + sKeyword[NF_KEY_YYYY] = "JJJJ"; + sKeyword[NF_KEY_BOOLEAN] = "LOGISCH"; + sKeyword[NF_KEY_COLOR] = GermanColorName(NF_KEY_COLOR - NF_KEY_COLOR); + sKeyword[NF_KEY_BLACK] = GermanColorName(NF_KEY_BLACK - NF_KEY_COLOR); + sKeyword[NF_KEY_BLUE] = GermanColorName(NF_KEY_BLUE - NF_KEY_COLOR); + sKeyword[NF_KEY_GREEN] = GermanColorName(NF_KEY_GREEN - NF_KEY_COLOR); + sKeyword[NF_KEY_CYAN] = GermanColorName(NF_KEY_CYAN - NF_KEY_COLOR); + sKeyword[NF_KEY_RED] = GermanColorName(NF_KEY_RED - NF_KEY_COLOR); + sKeyword[NF_KEY_MAGENTA] = GermanColorName(NF_KEY_MAGENTA - NF_KEY_COLOR); + sKeyword[NF_KEY_BROWN] = GermanColorName(NF_KEY_BROWN - NF_KEY_COLOR); + sKeyword[NF_KEY_GREY] = GermanColorName(NF_KEY_GREY - NF_KEY_COLOR); + sKeyword[NF_KEY_YELLOW] = GermanColorName(NF_KEY_YELLOW - NF_KEY_COLOR); + sKeyword[NF_KEY_WHITE] = GermanColorName(NF_KEY_WHITE - NF_KEY_COLOR); + } + else + { + // day + if ( eLang.anyOf( + LANGUAGE_ITALIAN, + LANGUAGE_ITALIAN_SWISS)) + { + sKeyword[NF_KEY_D] = "G"; + sKeyword[NF_KEY_DD] = "GG"; + sKeyword[NF_KEY_DDD] = "GGG"; + sKeyword[NF_KEY_DDDD] = "GGGG"; + // must exchange the era code, same as Xcl + sKeyword[NF_KEY_G] = "X"; + sKeyword[NF_KEY_GG] = "XX"; + sKeyword[NF_KEY_GGG] = "XXX"; + } + else if ( eLang.anyOf( + LANGUAGE_FRENCH, + LANGUAGE_FRENCH_BELGIAN, + LANGUAGE_FRENCH_CANADIAN, + LANGUAGE_FRENCH_SWISS, + LANGUAGE_FRENCH_LUXEMBOURG, + LANGUAGE_FRENCH_MONACO)) + { + sKeyword[NF_KEY_D] = "J"; + sKeyword[NF_KEY_DD] = "JJ"; + sKeyword[NF_KEY_DDD] = "JJJ"; + sKeyword[NF_KEY_DDDD] = "JJJJ"; + } + else if ( eLang == LANGUAGE_FINNISH ) + { + sKeyword[NF_KEY_D] = "P"; + sKeyword[NF_KEY_DD] = "PP"; + sKeyword[NF_KEY_DDD] = "PPP"; + sKeyword[NF_KEY_DDDD] = "PPPP"; + } + + // month + if ( eLang == LANGUAGE_FINNISH ) + { + sKeyword[NF_KEY_M] = "K"; + sKeyword[NF_KEY_MM] = "KK"; + sKeyword[NF_KEY_MMM] = "KKK"; + sKeyword[NF_KEY_MMMM] = "KKKK"; + sKeyword[NF_KEY_MMMMM] = "KKKKK"; + } + + // year + if ( eLang.anyOf( + LANGUAGE_ITALIAN, + LANGUAGE_ITALIAN_SWISS, + LANGUAGE_FRENCH, + LANGUAGE_FRENCH_BELGIAN, + LANGUAGE_FRENCH_CANADIAN, + LANGUAGE_FRENCH_SWISS, + LANGUAGE_FRENCH_LUXEMBOURG, + LANGUAGE_FRENCH_MONACO, + LANGUAGE_PORTUGUESE, + LANGUAGE_PORTUGUESE_BRAZILIAN, + LANGUAGE_SPANISH_MODERN, + LANGUAGE_SPANISH_DATED, + LANGUAGE_SPANISH_MEXICAN, + LANGUAGE_SPANISH_GUATEMALA, + LANGUAGE_SPANISH_COSTARICA, + LANGUAGE_SPANISH_PANAMA, + LANGUAGE_SPANISH_DOMINICAN_REPUBLIC, + LANGUAGE_SPANISH_VENEZUELA, + LANGUAGE_SPANISH_COLOMBIA, + LANGUAGE_SPANISH_PERU, + LANGUAGE_SPANISH_ARGENTINA, + LANGUAGE_SPANISH_ECUADOR, + LANGUAGE_SPANISH_CHILE, + LANGUAGE_SPANISH_URUGUAY, + LANGUAGE_SPANISH_PARAGUAY, + LANGUAGE_SPANISH_BOLIVIA, + LANGUAGE_SPANISH_EL_SALVADOR, + LANGUAGE_SPANISH_HONDURAS, + LANGUAGE_SPANISH_NICARAGUA, + LANGUAGE_SPANISH_PUERTO_RICO)) + { + sKeyword[NF_KEY_YY] = "AA"; + sKeyword[NF_KEY_YYYY] = "AAAA"; + // must exchange the day of week name code, same as Xcl + sKeyword[NF_KEY_AAA] = "OOO"; + sKeyword[NF_KEY_AAAA] = "OOOO"; + } + else if ( eLang.anyOf( + LANGUAGE_DUTCH, + LANGUAGE_DUTCH_BELGIAN)) + { + sKeyword[NF_KEY_YY] = "JJ"; + sKeyword[NF_KEY_YYYY] = "JJJJ"; + } + else if ( eLang == LANGUAGE_FINNISH ) + { + sKeyword[NF_KEY_YY] = "VV"; + sKeyword[NF_KEY_YYYY] = "VVVV"; + } + + // hour + if ( eLang.anyOf( + LANGUAGE_DUTCH, + LANGUAGE_DUTCH_BELGIAN)) + { + sKeyword[NF_KEY_H] = "U"; + sKeyword[NF_KEY_HH] = "UU"; + } + else if ( eLang.anyOf( + LANGUAGE_FINNISH, + LANGUAGE_SWEDISH, + LANGUAGE_SWEDISH_FINLAND, + LANGUAGE_DANISH, + LANGUAGE_NORWEGIAN, + LANGUAGE_NORWEGIAN_BOKMAL, + LANGUAGE_NORWEGIAN_NYNORSK)) + { + sKeyword[NF_KEY_H] = "T"; + sKeyword[NF_KEY_HH] = "TT"; + } + } +} + +void ImpSvNumberformatScan::ChangeNullDate(sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear) +{ + maNullDate = Date(nDay, nMonth, nYear); + if (!maNullDate.IsValidDate()) + { + maNullDate.Normalize(); + SAL_WARN("svl.numbers","ImpSvNumberformatScan::ChangeNullDate - not valid" + " d: " << nDay << " m: " << nMonth << " y: " << nYear << " normalized to" + " d: " << maNullDate.GetDay() << " m: " << maNullDate.GetMonth() << " y: " << maNullDate.GetYear()); + } +} + +void ImpSvNumberformatScan::ChangeStandardPrec(sal_uInt16 nPrec) +{ + nStandardPrec = nPrec; +} + +const Color* ImpSvNumberformatScan::GetColor(OUString& sStr) const +{ + OUString sString = pFormatter->GetCharClass()->uppercase(sStr); + const NfKeywordTable & rKeyword = GetKeywords(); + size_t i = 0; + while (i < NF_MAX_DEFAULT_COLORS && sString != rKeyword[NF_KEY_FIRSTCOLOR+i] ) + { + i++; + } + if (i >= NF_MAX_DEFAULT_COLORS && meKeywordLocalization == KeywordLocalization::AllowEnglish) + { + LanguageType eLang = pFormatter->GetLocaleData()->getLoadedLanguageTag().getLanguageType( false); + if ( eLang.anyOf( + LANGUAGE_GERMAN, + LANGUAGE_GERMAN_SWISS, + LANGUAGE_GERMAN_AUSTRIAN, + LANGUAGE_GERMAN_LUXEMBOURG, + LANGUAGE_GERMAN_LIECHTENSTEIN )) // only German uses localized color names + { + size_t j = 0; + while ( j < NF_MAX_DEFAULT_COLORS && sString != sEnglishKeyword[NF_KEY_FIRSTCOLOR + j] ) + { + ++j; + } + if ( j < NF_MAX_DEFAULT_COLORS ) + { + i = j; + } + } + } + + enum ColorKeywordConversion + { + None, + GermanToEnglish, + EnglishToGerman + } eColorKeywordConversion(None); + + if (bConvertMode) + { + const bool bFromGerman = eTmpLnge.anyOf( + LANGUAGE_GERMAN, + LANGUAGE_GERMAN_SWISS, + LANGUAGE_GERMAN_AUSTRIAN, + LANGUAGE_GERMAN_LUXEMBOURG, + LANGUAGE_GERMAN_LIECHTENSTEIN); + const bool bToGerman = eNewLnge.anyOf( + LANGUAGE_GERMAN, + LANGUAGE_GERMAN_SWISS, + LANGUAGE_GERMAN_AUSTRIAN, + LANGUAGE_GERMAN_LUXEMBOURG, + LANGUAGE_GERMAN_LIECHTENSTEIN); + if (bFromGerman && !bToGerman) + eColorKeywordConversion = ColorKeywordConversion::GermanToEnglish; + else if (!bFromGerman && bToGerman) + eColorKeywordConversion = ColorKeywordConversion::EnglishToGerman; + } + + const Color* pResult = nullptr; + if (i >= NF_MAX_DEFAULT_COLORS) + { + const OUString& rColorWord = rKeyword[NF_KEY_COLOR]; + bool bL10n = true; + if ((bL10n = sString.startsWith(rColorWord)) || + ((meKeywordLocalization == KeywordLocalization::AllowEnglish) && + sString.startsWith(sEnglishKeyword[NF_KEY_COLOR]))) + { + sal_Int32 nPos = (bL10n ? rColorWord.getLength() : sEnglishKeyword[NF_KEY_COLOR].getLength()); + sStr = sStr.copy(nPos); + sStr = comphelper::string::strip(sStr, ' '); + switch (eColorKeywordConversion) + { + case ColorKeywordConversion::None: + sStr = rColorWord + sStr; + break; + case ColorKeywordConversion::GermanToEnglish: + sStr = sEnglishKeyword[NF_KEY_COLOR] + sStr; // Farbe -> COLOR + break; + case ColorKeywordConversion::EnglishToGerman: + sStr = GermanColorName(NF_KEY_COLOR - NF_KEY_COLOR) + sStr; // Color -> FARBE + break; + } + sString = sString.copy(nPos); + sString = comphelper::string::strip(sString, ' '); + + if ( CharClass::isAsciiNumeric( sString ) ) + { + sal_Int32 nIndex = sString.toInt32(); + if (nIndex > 0 && nIndex <= 64) + { + pResult = pFormatter->GetUserDefColor(static_cast(nIndex)-1); + } + } + } + } + else + { + sStr.clear(); + switch (eColorKeywordConversion) + { + case ColorKeywordConversion::None: + sStr = rKeyword[NF_KEY_FIRSTCOLOR+i]; + break; + case ColorKeywordConversion::GermanToEnglish: + sStr = sEnglishKeyword[NF_KEY_FIRSTCOLOR + i]; // Rot -> RED + break; + case ColorKeywordConversion::EnglishToGerman: + sStr = GermanColorName(NF_KEY_FIRSTCOLOR - NF_KEY_COLOR + i); // Red -> ROT + break; + } + pResult = &(StandardColor[i]); + } + return pResult; +} + +short ImpSvNumberformatScan::GetKeyWord( const OUString& sSymbol, sal_Int32 nPos, bool& rbFoundEnglish ) const +{ + OUString sString = pFormatter->GetCharClass()->uppercase( sSymbol, nPos, sSymbol.getLength() - nPos ); + const NfKeywordTable & rKeyword = GetKeywords(); + // #77026# for the Xcl perverts: the GENERAL keyword is recognized anywhere + if (sString.startsWith( rKeyword[NF_KEY_GENERAL] )) + { + return NF_KEY_GENERAL; + } + if ((meKeywordLocalization == KeywordLocalization::AllowEnglish) && + sString.startsWith( sEnglishKeyword[NF_KEY_GENERAL])) + { + rbFoundEnglish = true; + return NF_KEY_GENERAL; + } + + // MUST be a reverse search to find longer strings first, + // new keywords take precedence over old keywords, + // skip colors et al after keywords. + short i = NF_KEY_LASTKEYWORD; + while (i > 0 && !sString.startsWith( rKeyword[i])) + { + i--; + } + if (i == 0 && meKeywordLocalization == KeywordLocalization::AllowEnglish) + { + // No localized (if so) keyword, try English keywords if keywords + // are localized. That was already checked in SetDependentKeywords(). + i = NF_KEY_LASTKEYWORD; + while (i > 0 && !sString.startsWith( sEnglishKeyword[i])) + { + i--; + } + } + + // The Thai T NatNum modifier during Xcl import. + if (i == 0 && bConvertMode && + sString[0] == 'T' && + eTmpLnge == LANGUAGE_ENGLISH_US && + MsLangId::getRealLanguage( eNewLnge) == LANGUAGE_THAI) + { + i = NF_KEY_THAI_T; + } + return i; // 0 => not found +} + +/** + * Next_Symbol + * + * Splits up the input for further processing (by the Turing machine). + * + * Starting state = SsStar + * + * ---------------+-------------------+---------------------------+--------------- + * Old state | Character read | Event | New state + * ---------------+-------------------+---------------------------+--------------- + * SsStart | Character | Symbol = Character | SsGetWord + * | " | Type = String | SsGetString + * | \ | Type = String | SsGetChar + * | * | Type = Star | SsGetStar + * | _ | Type = Blank | SsGetBlank + * | @ # 0 ? / . , % [ | Symbol = Character; | + * | ] ' Blank | Type = Control character | SsStop + * | $ - + ( ) : | Type = String; | + * | Else | Symbol = Character | SsStop + * ---------------|-------------------+---------------------------+--------------- + * SsGetChar | Else | Symbol = Character | SsStop + * ---------------+-------------------+---------------------------+--------------- + * GetString | " | | SsStop + * | Else | Symbol += Character | GetString + * ---------------+-------------------+---------------------------+--------------- + * SsGetWord | Character | Symbol += Character | + * | + - (E+ E-)| Symbol += Character | SsStop + * | / (AM/PM)| Symbol += Character | + * | Else | Pos--, if Key Type = Word | SsStop + * ---------------+-------------------+---------------------------+--------------- + * SsGetStar | Else | Symbol += Character | SsStop + * | | Mark special case * | + * ---------------+-------------------+---------------------------+--------------- + * SsGetBlank | Else | Symbol + =Character | SsStop + * | | Mark special case _ | + * ---------------------------------------------------------------+-------------- + * + * If we recognize a keyword in the state SsGetWord (even as the symbol's start text) + * we write back the rest of the characters! + */ + +namespace { + +enum ScanState +{ + SsStop = 0, + SsStart = 1, + SsGetChar = 2, + SsGetString = 3, + SsGetWord = 4, + SsGetStar = 5, + SsGetBlank = 6 +}; + +} + +short ImpSvNumberformatScan::Next_Symbol( const OUString& rStr, + sal_Int32& nPos, + OUString& sSymbol ) const +{ + InitKeywords(); + const CharClass* pChrCls = pFormatter->GetCharClass(); + const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData(); + short eType = 0; + ScanState eState = SsStart; + OUStringBuffer sSymbolBuffer; + while ( nPos < rStr.getLength() && eState != SsStop ) + { + sal_Unicode cToken = rStr[nPos++]; + switch (eState) + { + case SsStart: + // Fetch any currency longer than one character and don't get + // confused later on by "E/" or other combinations of letters + // and meaningful symbols. Necessary for old automatic currency. + // #96158# But don't do it if we're starting a "[...]" section, + // for example a "[$...]" new currency symbol to not parse away + // "$U" (symbol) of "[$UYU]" (abbreviation). + if ( nCurrPos >= 0 && sCurString.getLength() > 1 && + nPos-1 + sCurString.getLength() <= rStr.getLength() && + (nPos <= 1 || rStr[nPos-2] != '[') ) + { + OUString aTest = pChrCls->uppercase( rStr.copy( nPos-1, sCurString.getLength() ) ); + if ( aTest == sCurString ) + { + sSymbol = rStr.copy( --nPos, sCurString.getLength() ); + nPos = nPos + sSymbol.getLength(); + eType = NF_SYMBOLTYPE_STRING; + return eType; + } + } + switch (cToken) + { + case '#': + case '0': + case '?': + case '%': + case '@': + case '[': + case ']': + case ',': + case '.': + case '/': + case '\'': + case ' ': + case ':': + case '-': + eType = NF_SYMBOLTYPE_DEL; + sSymbolBuffer.append(OUStringChar(cToken)); + eState = SsStop; + break; + case '*': + eType = NF_SYMBOLTYPE_STAR; + sSymbolBuffer.append(OUStringChar(cToken)); + eState = SsGetStar; + break; + case '_': + eType = NF_SYMBOLTYPE_BLANK; + sSymbolBuffer.append(OUStringChar(cToken)); + eState = SsGetBlank; + break; + case '"': + eType = NF_SYMBOLTYPE_STRING; + eState = SsGetString; + sSymbolBuffer.append(OUStringChar(cToken)); + break; + case '\\': + eType = NF_SYMBOLTYPE_STRING; + eState = SsGetChar; + sSymbolBuffer.append(OUStringChar(cToken)); + break; + case '$': + case '+': + case '(': + case ')': + eType = NF_SYMBOLTYPE_STRING; + eState = SsStop; + sSymbolBuffer.append(OUStringChar(cToken)); + break; + default : + if (StringEqualsChar( pFormatter->GetNumDecimalSep(), cToken) || + StringEqualsChar( pFormatter->GetNumThousandSep(), cToken) || + StringEqualsChar( pFormatter->GetDateSep(), cToken) || + StringEqualsChar( pLoc->getTimeSep(), cToken) || + StringEqualsChar( pLoc->getTime100SecSep(), cToken)) + { + // Another separator than pre-known ASCII + eType = NF_SYMBOLTYPE_DEL; + sSymbolBuffer.append(OUStringChar(cToken)); + eState = SsStop; + } + else if ( pChrCls->isLetter( rStr, nPos-1 ) ) + { + bool bFoundEnglish = false; + short nTmpType = GetKeyWord( rStr, nPos-1, bFoundEnglish); + if ( nTmpType ) + { + bool bCurrency = false; + // "Automatic" currency may start with keyword, + // like "R" (Rand) and 'R' (era) + if ( nCurrPos >= 0 && + nPos-1 + sCurString.getLength() <= rStr.getLength() && + sCurString.startsWith( bFoundEnglish ? sEnglishKeyword[nTmpType] : sKeyword[nTmpType])) + { + OUString aTest = pChrCls->uppercase( rStr.copy( nPos-1, sCurString.getLength() ) ); + if ( aTest == sCurString ) + { + bCurrency = true; + } + } + if ( bCurrency ) + { + eState = SsGetWord; + sSymbolBuffer.append(OUStringChar(cToken)); + } + else + { + eType = nTmpType; + // The code to be advanced is the detected keyword, + // not necessarily the locale's keyword, but the + // symbol is to be the locale's keyword. + sal_Int32 nLen; + if (bFoundEnglish) + { + nLen = sEnglishKeyword[eType].getLength(); + // Use the locale's General keyword name, not uppercase. + sSymbolBuffer = (eType == NF_KEY_GENERAL ? sNameStandardFormat : sKeyword[eType]); + } + else + { + nLen = sKeyword[eType].getLength(); + // Preserve a locale's keyword's case as entered. + sSymbolBuffer = rStr.subView( nPos-1, nLen); + } + if ((eType == NF_KEY_E || IsAmbiguousE(eType)) && nPos < rStr.getLength()) + { + sal_Unicode cNext = rStr[nPos]; + switch ( cNext ) + { + case '+' : + case '-' : // E+ E- combine to one symbol + sSymbolBuffer.append(OUStringChar(cNext)); + eType = NF_KEY_E; + nPos++; + break; + case '0' : + case '#' : // scientific E without sign + eType = NF_KEY_E; + break; + } + } + nPos--; + nPos = nPos + nLen; + eState = SsStop; + } + } + else + { + eState = SsGetWord; + sSymbolBuffer.append(OUStringChar(cToken)); + } + } + else + { + eType = NF_SYMBOLTYPE_STRING; + eState = SsStop; + sSymbolBuffer.append(OUStringChar(cToken)); + } + break; + } + break; + case SsGetChar: + sSymbolBuffer.append(OUStringChar(cToken)); + eState = SsStop; + break; + case SsGetString: + if (cToken == '"') + { + eState = SsStop; + } + sSymbolBuffer.append(OUStringChar(cToken)); + break; + case SsGetWord: + if ( pChrCls->isLetter( rStr, nPos-1 ) ) + { + bool bFoundEnglish = false; + short nTmpType = GetKeyWord( rStr, nPos-1, bFoundEnglish); + if ( nTmpType ) + { + // beginning of keyword, stop scan and put back + eType = NF_SYMBOLTYPE_STRING; + eState = SsStop; + nPos--; + } + else + { + sSymbolBuffer.append(OUStringChar(cToken)); + } + } + else + { + bool bDontStop = false; + sal_Unicode cNext; + switch (cToken) + { + case '/': // AM/PM, A/P + if (nPos < rStr.getLength()) + { + cNext = rStr[nPos]; + if ( cNext == 'P' || cNext == 'p' ) + { + sal_Int32 nLen = sSymbolBuffer.getLength(); + if ( 1 <= nLen && + (sSymbolBuffer[0] == 'A' || sSymbolBuffer[0] == 'a') && + (nLen == 1 || + (nLen == 2 && (sSymbolBuffer[1] == 'M' || sSymbolBuffer[1] == 'm') + && (rStr[nPos + 1] == 'M' || rStr[nPos + 1] == 'm')))) + { + sSymbolBuffer.append(OUStringChar(cToken)); + bDontStop = true; + } + } + } + break; + } + // anything not recognized will stop the scan + if (!bDontStop) + { + eState = SsStop; + nPos--; + eType = NF_SYMBOLTYPE_STRING; + } + } + break; + case SsGetStar: + eState = SsStop; + sSymbolBuffer.append(OUStringChar(cToken)); + break; + case SsGetBlank: + eState = SsStop; + sSymbolBuffer.append(OUStringChar(cToken)); + break; + default: + break; + } // of switch + } // of while + if (eState == SsGetWord) + { + eType = NF_SYMBOLTYPE_STRING; + } + sSymbol = sSymbolBuffer.makeStringAndClear(); + return eType; +} + +sal_Int32 ImpSvNumberformatScan::Symbol_Division(const OUString& rString) +{ + nCurrPos = -1; + // Do we have some sort of currency? + OUString sString = pFormatter->GetCharClass()->uppercase(rString); + sal_Int32 nCPos = 0; + while (nCPos >= 0 && nCPos < sString.getLength()) + { + nCPos = sString.indexOf(GetCurString(),nCPos); + if (nCPos >= 0) + { + // In Quotes? + sal_Int32 nQ = SvNumberformat::GetQuoteEnd( sString, nCPos ); + if ( nQ < 0 ) + { + sal_Unicode c; + if ( nCPos == 0 || + ((c = sString[nCPos-1]) != '"' + && c != '\\') ) // dm can be protected by "dm \d + { + nCurrPos = nCPos; + nCPos = -1; + } + else + { + nCPos++; // Continue search + } + } + else + { + nCPos = nQ + 1; // Continue search + } + } + } + nStringsCnt = 0; + bool bStar = false; // Is set on detecting '*' + Reset(); + + sal_Int32 nPos = 0; + const sal_Int32 nLen = rString.getLength(); + while (nPos < nLen && nStringsCnt < NF_MAX_FORMAT_SYMBOLS) + { + nTypeArray[nStringsCnt] = Next_Symbol(rString, nPos, sStrArray[nStringsCnt]); + if (nTypeArray[nStringsCnt] == NF_SYMBOLTYPE_STAR) + { // Monitoring the '*' + if (bStar) + { + return nPos; // Error: double '*' + } + else + { + // Valid only if there is a character following, else we are + // at the end of a code that does not have a fill character + // (yet?). + if (sStrArray[nStringsCnt].getLength() < 2) + return nPos; + bStar = true; + } + } + nStringsCnt++; + } + + return 0; // 0 => ok +} + +void ImpSvNumberformatScan::SkipStrings(sal_uInt16& i, sal_Int32& nPos) const +{ + while (i < nStringsCnt && ( nTypeArray[i] == NF_SYMBOLTYPE_STRING + || nTypeArray[i] == NF_SYMBOLTYPE_BLANK + || nTypeArray[i] == NF_SYMBOLTYPE_STAR) ) + { + nPos = nPos + sStrArray[i].getLength(); + i++; + } +} + +sal_uInt16 ImpSvNumberformatScan::PreviousKeyword(sal_uInt16 i) const +{ + short res = 0; + if (i > 0 && i < nStringsCnt) + { + i--; + while (i > 0 && nTypeArray[i] <= 0) + { + i--; + } + if (nTypeArray[i] > 0) + { + res = nTypeArray[i]; + } + } + return res; +} + +sal_uInt16 ImpSvNumberformatScan::NextKeyword(sal_uInt16 i) const +{ + short res = 0; + if (i < nStringsCnt-1) + { + i++; + while (i < nStringsCnt-1 && nTypeArray[i] <= 0) + { + i++; + } + if (nTypeArray[i] > 0) + { + res = nTypeArray[i]; + } + } + return res; +} + +short ImpSvNumberformatScan::PreviousType( sal_uInt16 i ) const +{ + if ( i > 0 && i < nStringsCnt ) + { + do + { + i--; + } + while ( i > 0 && nTypeArray[i] == NF_SYMBOLTYPE_EMPTY ); + return nTypeArray[i]; + } + return 0; +} + +sal_Unicode ImpSvNumberformatScan::PreviousChar(sal_uInt16 i) const +{ + sal_Unicode res = ' '; + if (i > 0 && i < nStringsCnt) + { + i--; + while (i > 0 && + ( nTypeArray[i] == NF_SYMBOLTYPE_EMPTY || + nTypeArray[i] == NF_SYMBOLTYPE_STRING || + nTypeArray[i] == NF_SYMBOLTYPE_STAR || + nTypeArray[i] == NF_SYMBOLTYPE_BLANK )) + { + i--; + } + if (sStrArray[i].getLength() > 0) + { + res = sStrArray[i][sStrArray[i].getLength()-1]; + } + } + return res; +} + +sal_Unicode ImpSvNumberformatScan::NextChar(sal_uInt16 i) const +{ + sal_Unicode res = ' '; + if (i < nStringsCnt-1) + { + i++; + while (i < nStringsCnt-1 && + ( nTypeArray[i] == NF_SYMBOLTYPE_EMPTY || + nTypeArray[i] == NF_SYMBOLTYPE_STRING || + nTypeArray[i] == NF_SYMBOLTYPE_STAR || + nTypeArray[i] == NF_SYMBOLTYPE_BLANK)) + { + i++; + } + if (sStrArray[i].getLength() > 0) + { + res = sStrArray[i][0]; + } + } + return res; +} + +bool ImpSvNumberformatScan::IsLastBlankBeforeFrac(sal_uInt16 i) const +{ + bool res = true; + if (i < nStringsCnt-1) + { + bool bStop = false; + i++; + while (i < nStringsCnt-1 && !bStop) + { + i++; + if ( nTypeArray[i] == NF_SYMBOLTYPE_DEL && + sStrArray[i][0] == '/') + { + bStop = true; + } + else if ( ( nTypeArray[i] == NF_SYMBOLTYPE_DEL && + sStrArray[i][0] == ' ') || + nTypeArray[i] == NF_SYMBOLTYPE_STRING ) // integer/fraction delimiter can also be a string + { + res = false; + } + } + if (!bStop) // no '/'{ + { + res = false; + } + } + else + { + res = false; // no '/' any more + } + return res; +} + +void ImpSvNumberformatScan::Reset() +{ + nStringsCnt = 0; + nResultStringsCnt = 0; + eScannedType = SvNumFormatType::UNDEFINED; + bExp = false; + bThousand = false; + nThousand = 0; + bDecSep = false; + nDecPos = sal_uInt16(-1); + nExpPos = sal_uInt16(-1); + nBlankPos = sal_uInt16(-1); + nCntPre = 0; + nCntPost = 0; + nCntExp = 0; + bFrac = false; + bBlank = false; + nNatNumModifier = 0; +} + +bool ImpSvNumberformatScan::Is100SecZero( sal_uInt16 i, bool bHadDecSep ) const +{ + sal_uInt16 nIndexPre = PreviousKeyword( i ); + return (nIndexPre == NF_KEY_S || nIndexPre == NF_KEY_SS) && + (bHadDecSep || + ( i > 0 && nTypeArray[i-1] == NF_SYMBOLTYPE_STRING)); + // SS"any"00 take "any" as a valid decimal separator +} + +sal_Int32 ImpSvNumberformatScan::ScanType() +{ + const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData(); + + sal_Int32 nPos = 0; + sal_uInt16 i = 0; + SvNumFormatType eNewType; + bool bMatchBracket = false; + bool bHaveGeneral = false; // if General/Standard encountered + bool bIsTimeDetected =false; // hour or second found in format + bool bHaveMinute = false; + + SkipStrings(i, nPos); + while (i < nStringsCnt) + { + if (nTypeArray[i] > 0) + { // keyword + sal_uInt16 nIndexPre; + sal_uInt16 nIndexNex; + + switch (nTypeArray[i]) + { + case NF_KEY_E: // E + eNewType = SvNumFormatType::SCIENTIFIC; + break; + case NF_KEY_H: // H + case NF_KEY_HH: // HH + bIsTimeDetected = true; + [[fallthrough]]; + case NF_KEY_S: // S + case NF_KEY_SS: // SS + if ( !bHaveMinute ) + bIsTimeDetected = true; + [[fallthrough]]; + case NF_KEY_AMPM: // AM,A,PM,P + case NF_KEY_AP: + eNewType = SvNumFormatType::TIME; + break; + case NF_KEY_M: // M + case NF_KEY_MM: // MM + case NF_KEY_MI: // M minute detected in Finnish + case NF_KEY_MMI: // MM + /* Minute or month. + Minute if one of: + * preceded by time keyword H (ignoring separators) + * followed by time keyword S (ignoring separators) + * H or S was detected and this is the first M following + * preceded by '[' amount bracket + Else month. + That are the Excel rules. BUT, we break it because certainly + in something like {HH YYYY-MM-DD} the MM is NOT meant to be + minute, so not if MM is between YY and DD or DD and YY. + Actually not if any date specific keyword followed a time + setting keyword. + */ + nIndexPre = PreviousKeyword(i); + nIndexNex = NextKeyword(i); + if (nIndexPre == NF_KEY_H || // H + nIndexPre == NF_KEY_HH || // HH + nIndexNex == NF_KEY_S || // S + nIndexNex == NF_KEY_SS || // SS + bIsTimeDetected || // tdf#101147 + PreviousChar(i) == '[' ) // [M + { + eNewType = SvNumFormatType::TIME; + if ( nTypeArray[i] == NF_KEY_M || nTypeArray[i] == NF_KEY_MM ) + { + nTypeArray[i] -= 2; // 6 -> 4, 7 -> 5 + } + bIsTimeDetected = false; // next M should be month + bHaveMinute = true; + } + else + { + eNewType = SvNumFormatType::DATE; + if ( nTypeArray[i] == NF_KEY_MI || nTypeArray[i] == NF_KEY_MMI ) + { // follow resolution of tdf#33689 for Finnish + nTypeArray[i] += 2; // 4 -> 6, 5 -> 7 + } + } + break; + case NF_KEY_MMM: // MMM + case NF_KEY_MMMM: // MMMM + case NF_KEY_MMMMM: // MMMMM + case NF_KEY_Q: // Q + case NF_KEY_QQ: // QQ + case NF_KEY_D: // D + case NF_KEY_DD: // DD + case NF_KEY_DDD: // DDD + case NF_KEY_DDDD: // DDDD + case NF_KEY_YY: // YY + case NF_KEY_YYYY: // YYYY + case NF_KEY_NN: // NN + case NF_KEY_NNN: // NNN + case NF_KEY_NNNN: // NNNN + case NF_KEY_WW : // WW + case NF_KEY_AAA : // AAA + case NF_KEY_AAAA : // AAAA + case NF_KEY_EC : // E + case NF_KEY_EEC : // EE + case NF_KEY_G : // G + case NF_KEY_GG : // GG + case NF_KEY_GGG : // GGG + case NF_KEY_R : // R + case NF_KEY_RR : // RR + eNewType = SvNumFormatType::DATE; + bIsTimeDetected = false; + break; + case NF_KEY_CCC: // CCC + eNewType = SvNumFormatType::CURRENCY; + break; + case NF_KEY_BOOLEAN: // BOOLEAN + eNewType = SvNumFormatType::LOGICAL; + break; + case NF_KEY_GENERAL: // General + eNewType = SvNumFormatType::NUMBER; + bHaveGeneral = true; + break; + default: + eNewType = SvNumFormatType::UNDEFINED; + break; + } + } + else + { // control character + switch ( sStrArray[i][0] ) + { + case '#': + case '?': + eNewType = SvNumFormatType::NUMBER; + break; + case '0': + if ( eScannedType & SvNumFormatType::TIME ) + { + if ( Is100SecZero( i, bDecSep ) ) + { + bDecSep = true; // subsequent 0's + eNewType = SvNumFormatType::TIME; + } + else + { + return nPos; // Error + } + } + else + { + eNewType = SvNumFormatType::NUMBER; + } + break; + case '%': + eNewType = SvNumFormatType::PERCENT; + break; + case '/': + eNewType = SvNumFormatType::FRACTION; + break; + case '[': + if ( i < nStringsCnt-1 && + nTypeArray[i+1] == NF_SYMBOLTYPE_STRING && + sStrArray[i+1][0] == '$' ) + { + eNewType = SvNumFormatType::CURRENCY; + bMatchBracket = true; + } + else if ( i < nStringsCnt-1 && + nTypeArray[i+1] == NF_SYMBOLTYPE_STRING && + sStrArray[i+1][0] == '~' ) + { + eNewType = SvNumFormatType::DATE; + bMatchBracket = true; + } + else + { + sal_uInt16 nIndexNex = NextKeyword(i); + if (nIndexNex == NF_KEY_H || // H + nIndexNex == NF_KEY_HH || // HH + nIndexNex == NF_KEY_M || // M + nIndexNex == NF_KEY_MM || // MM + nIndexNex == NF_KEY_S || // S + nIndexNex == NF_KEY_SS ) // SS + eNewType = SvNumFormatType::TIME; + else + { + return nPos; // Error + } + } + break; + case '@': + eNewType = SvNumFormatType::TEXT; + break; + default: + // Separator for SS,0 + if ((eScannedType & SvNumFormatType::TIME) + && 0 < i && (nTypeArray[i-1] == NF_KEY_S || nTypeArray[i-1] == NF_KEY_SS)) + { + // For ISO 8601 only YYYY-MM-DD"T"HH:MM:SS,0 accept both + // ',' and '.' regardless of locale's separator, and only + // those. + // XXX NOTE: this catches only known separators of + // NF_SYMBOLTYPE_DEL as all NF_SYMBOLTYPE_STRING are + // skipped during the loop. Meant to error out if the + // Time100SecSep or decimal separator differ and were used. + if ((eScannedType & SvNumFormatType::DATE) + && 11 <= i && i < nStringsCnt-1 + && (nTypeArray[i-6] == NF_SYMBOLTYPE_STRING + && (sStrArray[i-6] == "\"T\"" || sStrArray[i-6] == "\\T" + || sStrArray[i-6] == "T")) + && (nTypeArray[i-11] == NF_KEY_YYYY) + && (nTypeArray[i-9] == NF_KEY_M || nTypeArray[i-9] == NF_KEY_MM) + && (nTypeArray[i-7] == NF_KEY_D || nTypeArray[i-7] == NF_KEY_DD) + && (nTypeArray[i-5] == NF_KEY_H || nTypeArray[i-5] == NF_KEY_HH) + && (nTypeArray[i-3] == NF_KEY_MI || nTypeArray[i-3] == NF_KEY_MMI) + && (nTypeArray[i+1] == NF_SYMBOLTYPE_DEL && sStrArray[i+1][0] == '0')) + + { + if (sStrArray[i].getLength() == 1 && (sStrArray[i][0] == ',' || sStrArray[i][0] == '.')) + bDecSep = true; + else + return nPos; // Error + } + else if (pLoc->getTime100SecSep() == sStrArray[i]) + bDecSep = true; + } + eNewType = SvNumFormatType::UNDEFINED; + break; + } + } + if (eScannedType == SvNumFormatType::UNDEFINED) + { + eScannedType = eNewType; + } + else if (eScannedType == SvNumFormatType::TEXT || eNewType == SvNumFormatType::TEXT) + { + eScannedType = SvNumFormatType::TEXT; // Text always remains text + } + else if (eNewType == SvNumFormatType::UNDEFINED) + { // Remains as is + } + else if (eScannedType != eNewType) + { + switch (eScannedType) + { + case SvNumFormatType::DATE: + switch (eNewType) + { + case SvNumFormatType::TIME: + eScannedType = SvNumFormatType::DATETIME; + break; + case SvNumFormatType::FRACTION: // DD/MM + break; + default: + if (nCurrPos >= 0) + { + eScannedType = SvNumFormatType::UNDEFINED; + } + else if ( sStrArray[i] != pFormatter->GetDateSep() ) + { + return nPos; + } + } + break; + case SvNumFormatType::TIME: + switch (eNewType) + { + case SvNumFormatType::DATE: + eScannedType = SvNumFormatType::DATETIME; + break; + case SvNumFormatType::FRACTION: // MM/SS + break; + default: + if (nCurrPos >= 0) + { + eScannedType = SvNumFormatType::UNDEFINED; + } + else if (pLoc->getTimeSep() != sStrArray[i]) + { + return nPos; + } + break; + } + break; + case SvNumFormatType::DATETIME: + switch (eNewType) + { + case SvNumFormatType::TIME: + case SvNumFormatType::DATE: + break; + case SvNumFormatType::FRACTION: // DD/MM + break; + default: + if (nCurrPos >= 0) + { + eScannedType = SvNumFormatType::UNDEFINED; + } + else if ( pFormatter->GetDateSep() != sStrArray[i] && + pLoc->getTimeSep() != sStrArray[i] ) + { + return nPos; + } + } + break; + case SvNumFormatType::PERCENT: + switch (eNewType) + { + case SvNumFormatType::NUMBER: // Only number to percent + break; + default: + return nPos; + } + break; + case SvNumFormatType::SCIENTIFIC: + switch (eNewType) + { + case SvNumFormatType::NUMBER: // Only number to E + break; + default: + return nPos; + } + break; + case SvNumFormatType::NUMBER: + switch (eNewType) + { + case SvNumFormatType::SCIENTIFIC: + case SvNumFormatType::PERCENT: + case SvNumFormatType::FRACTION: + case SvNumFormatType::CURRENCY: + eScannedType = eNewType; + break; + default: + if (nCurrPos >= 0) + { + eScannedType = SvNumFormatType::UNDEFINED; + } + else + { + return nPos; + } + } + break; + case SvNumFormatType::FRACTION: + switch (eNewType) + { + case SvNumFormatType::NUMBER: // Only number to fraction + break; + default: + return nPos; + } + break; + default: + break; + } + } + nPos = nPos + sStrArray[i].getLength(); // Position of correction + i++; + if ( bMatchBracket ) + { // no type detection inside of matching brackets if [$...], [~...] + while ( bMatchBracket && i < nStringsCnt ) + { + if ( nTypeArray[i] == NF_SYMBOLTYPE_DEL + && sStrArray[i][0] == ']' ) + { + bMatchBracket = false; + } + else + { + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + } + nPos = nPos + sStrArray[i].getLength(); + i++; + } + if ( bMatchBracket ) + { + return nPos; // missing closing bracket at end of code + } + } + SkipStrings(i, nPos); + } + + if ((eScannedType == SvNumFormatType::NUMBER || + eScannedType == SvNumFormatType::UNDEFINED) && + nCurrPos >= 0 && !bHaveGeneral) + { + eScannedType = SvNumFormatType::CURRENCY; // old "automatic" currency + } + if (eScannedType == SvNumFormatType::UNDEFINED) + { + eScannedType = SvNumFormatType::DEFINED; + } + return 0; // All is fine +} + +bool ImpSvNumberformatScan::InsertSymbol( sal_uInt16 & nPos, svt::NfSymbolType eType, const OUString& rStr ) +{ + if (nStringsCnt >= NF_MAX_FORMAT_SYMBOLS || nPos > nStringsCnt) + { + return false; + } + if (nPos > 0 && nTypeArray[nPos-1] == NF_SYMBOLTYPE_EMPTY) + { + --nPos; // reuse position + } + else + { + if (nStringsCnt >= NF_MAX_FORMAT_SYMBOLS - 1) + { + return false; + } + ++nStringsCnt; + for (size_t i = nStringsCnt; i > nPos; --i) + { + nTypeArray[i] = nTypeArray[i-1]; + sStrArray[i] = sStrArray[i-1]; + } + } + ++nResultStringsCnt; + nTypeArray[nPos] = static_cast(eType); + sStrArray[nPos] = rStr; + return true; +} + +int ImpSvNumberformatScan::FinalScanGetCalendar( sal_Int32& nPos, sal_uInt16& i, + sal_uInt16& rResultStringsCnt ) +{ + if ( i < nStringsCnt-1 && + sStrArray[i][0] == '[' && + nTypeArray[i+1] == NF_SYMBOLTYPE_STRING && + sStrArray[i+1][0] == '~' ) + { + // [~calendarID] + nPos = nPos + sStrArray[i].getLength(); // [ + nTypeArray[i] = NF_SYMBOLTYPE_CALDEL; + nPos = nPos + sStrArray[++i].getLength(); // ~ + sStrArray[i-1] += sStrArray[i]; // [~ + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + rResultStringsCnt--; + if ( ++i >= nStringsCnt ) + { + return -1; // error + } + nPos = nPos + sStrArray[i].getLength(); // calendarID + OUString& rStr = sStrArray[i]; + nTypeArray[i] = NF_SYMBOLTYPE_CALENDAR; // convert + i++; + while ( i < nStringsCnt && sStrArray[i][0] != ']' ) + { + nPos = nPos + sStrArray[i].getLength(); + rStr += sStrArray[i]; + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + rResultStringsCnt--; + i++; + } + if ( rStr.getLength() && i < nStringsCnt && + sStrArray[i][0] == ']' ) + { + nTypeArray[i] = NF_SYMBOLTYPE_CALDEL; + nPos = nPos + sStrArray[i].getLength(); + i++; + } + else + { + return -1; // error + } + return 1; + } + return 0; +} + +bool ImpSvNumberformatScan::IsDateFragment( size_t nPos1, size_t nPos2 ) const +{ + return nPos2 - nPos1 == 2 && nTypeArray[nPos1+1] == NF_SYMBOLTYPE_DATESEP; +} + +void ImpSvNumberformatScan::SwapArrayElements( size_t nPos1, size_t nPos2 ) +{ + std::swap( nTypeArray[nPos1], nTypeArray[nPos2]); + std::swap( sStrArray[nPos1], sStrArray[nPos2]); +} + +sal_Int32 ImpSvNumberformatScan::FinalScan( OUString& rString ) +{ + const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData(); + + // save values for convert mode + OUString sOldDecSep = pFormatter->GetNumDecimalSep(); + OUString sOldThousandSep = pFormatter->GetNumThousandSep(); + OUString sOldDateSep = pFormatter->GetDateSep(); + OUString sOldTimeSep = pLoc->getTimeSep(); + OUString sOldTime100SecSep= pLoc->getTime100SecSep(); + OUString sOldCurSymbol = GetCurSymbol(); + OUString sOldCurString = GetCurString(); + sal_Unicode cOldKeyH = sKeyword[NF_KEY_H][0]; + sal_Unicode cOldKeyMI = sKeyword[NF_KEY_MI][0]; + sal_Unicode cOldKeyS = sKeyword[NF_KEY_S][0]; + DateOrder eOldDateOrder = pLoc->getDateOrder(); + sal_uInt16 nDayPos, nMonthPos, nYearPos; + nDayPos = nMonthPos = nYearPos = SAL_MAX_UINT16; + + // If the group separator is a No-Break Space (French) continue with a + // normal space instead so queries on space work correctly. + // The same for Narrow No-Break Space just in case some locale uses it. + // The format string is adjusted to allow both. + // For output of the format code string the LocaleData characters are used. + if ( (sOldThousandSep[0] == cNoBreakSpace || sOldThousandSep[0] == cNarrowNoBreakSpace) && + sOldThousandSep.getLength() == 1 ) + { + sOldThousandSep = " "; + } + bool bNewDateOrder = false; + // change locale data et al + if (bConvertMode) + { + pFormatter->ChangeIntl(eNewLnge); + //! pointer may have changed + pLoc = pFormatter->GetLocaleData(); + //! init new keywords + InitKeywords(); + // Adapt date order to target locale, but Excel does not handle date + // particle re-ordering for the target locale when loading documents, + // though it does exchange separators, tdf#113889 + bNewDateOrder = (mbConvertDateOrder && eOldDateOrder != pLoc->getDateOrder()); + } + const CharClass* pChrCls = pFormatter->GetCharClass(); + + sal_Int32 nPos = 0; // error correction position + sal_uInt16 i = 0; // symbol loop counter + sal_uInt16 nCounter = 0; // counts digits + nResultStringsCnt = nStringsCnt; // counts remaining symbols + bDecSep = false; // reset in case already used in TypeCheck + bool bThaiT = false; // Thai T NatNum modifier present + bool bTimePart = false; + bool bDenomin = false; // Set when reading end of denominator + + switch (eScannedType) + { + case SvNumFormatType::TEXT: + case SvNumFormatType::DEFINED: + while (i < nStringsCnt) + { + switch (nTypeArray[i]) + { + case NF_SYMBOLTYPE_BLANK: + case NF_SYMBOLTYPE_STAR: + break; + case NF_KEY_GENERAL : // #77026# "General" is the same as "@" + break; + default: + if ( nTypeArray[i] != NF_SYMBOLTYPE_DEL || + sStrArray[i][0] != '@' ) + { + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + } + break; + } + nPos = nPos + sStrArray[i].getLength(); + i++; + } // of while + break; + + case SvNumFormatType::NUMBER: + case SvNumFormatType::PERCENT: + case SvNumFormatType::CURRENCY: + case SvNumFormatType::SCIENTIFIC: + case SvNumFormatType::FRACTION: + while (i < nStringsCnt) + { + // TODO: rechecking eScannedType is unnecessary. + // This switch-case is for eScannedType == SvNumFormatType::FRACTION anyway + if (eScannedType == SvNumFormatType::FRACTION && // special case + nTypeArray[i] == NF_SYMBOLTYPE_DEL && // # ### #/# + StringEqualsChar( sOldThousandSep, ' ' ) && // e.g. France or Sweden + StringEqualsChar( sStrArray[i], ' ' ) && + !bFrac && + IsLastBlankBeforeFrac(i) ) + { + nTypeArray[i] = NF_SYMBOLTYPE_STRING; // del->string + } // No thousands marker + + if (nTypeArray[i] == NF_SYMBOLTYPE_BLANK || + nTypeArray[i] == NF_SYMBOLTYPE_STAR || + nTypeArray[i] == NF_KEY_CCC || // CCC + nTypeArray[i] == NF_KEY_GENERAL ) // Standard + { + if (nTypeArray[i] == NF_KEY_GENERAL) + { + nThousand = FLAG_STANDARD_IN_FORMAT; + if ( bConvertMode ) + { + sStrArray[i] = sNameStandardFormat; + } + } + nPos = nPos + sStrArray[i].getLength(); + i++; + } + else if (nTypeArray[i] == NF_SYMBOLTYPE_STRING || // No Strings or + nTypeArray[i] > 0) // Keywords + { + if (eScannedType == SvNumFormatType::SCIENTIFIC && + nTypeArray[i] == NF_KEY_E) // E+ + { + if (bExp) // Double + { + return nPos; + } + bExp = true; + nExpPos = i; + if (bDecSep) + { + nCntPost = nCounter; + } + else + { + nCntPre = nCounter; + } + nCounter = 0; + nTypeArray[i] = NF_SYMBOLTYPE_EXP; + } + else if (eScannedType == SvNumFormatType::FRACTION && + (sStrArray[i][0] == ' ' || ( nTypeArray[i] == NF_SYMBOLTYPE_STRING && (sStrArray[i][0] < '0' || sStrArray[i][0] > '9') ) ) ) + { + if (!bBlank && !bFrac) // Not double or after a / + { + if (bDecSep && nCounter > 0) // Decimal places + { + return nPos; // Error + } + if (sStrArray[i][0] == ' ' || nCounter > 0 ) // treat string as integer/fraction delimiter only if there is integer + { + bBlank = true; + nBlankPos = i; + nCntPre = nCounter; + nCounter = 0; + nTypeArray[i] = NF_SYMBOLTYPE_FRACBLANK; + } + } + else if ( sStrArray[i][0] == ' ' ) + nTypeArray[i] = NF_SYMBOLTYPE_FRACBLANK; + else if ( bFrac && ( nCounter > 0 ) ) + bDenomin = true; // following elements are no more part of denominator + } + else if (nTypeArray[i] == NF_KEY_THAI_T) + { + bThaiT = true; + sStrArray[i] = sKeyword[nTypeArray[i]]; + } + else if (sStrArray[i][0] >= '0' && + sStrArray[i][0] <= '9' && !bDenomin) // denominator was not yet found + { + OUString sDiv; + sal_uInt16 j = i; + while(j < nStringsCnt && sStrArray[j][0] >= '0' && sStrArray[j][0] <= '9') + { + sDiv += sStrArray[j++]; + } + assert(j > 0 && "if i is 0, first iteration through loop is guaranteed by surrounding if condition"); + if (std::u16string_view(OUString::number(sDiv.toInt32())) == sDiv) + { + // Found a Divisor + while (i < j) + { + nTypeArray[i++] = NF_SYMBOLTYPE_FRAC_FDIV; + } + i = j - 1; // Stop the loop + if (nCntPost) + { + nCounter = nCntPost; + } + else if (nCntPre) + { + nCounter = nCntPre; + } + // don't artificially increment nCntPre for forced denominator + if ( ( eScannedType != SvNumFormatType::FRACTION ) && (!nCntPre) ) + { + nCntPre++; + } + if ( bFrac ) + bDenomin = true; // next content should be treated as outside denominator + } + } + else + { + if ( bFrac && ( nCounter > 0 ) ) + bDenomin = true; // next content should be treated as outside denominator + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + } + nPos = nPos + sStrArray[i].getLength(); + i++; + } + else if (nTypeArray[i] == NF_SYMBOLTYPE_DEL) + { + sal_Unicode cHere = sStrArray[i][0]; + sal_Unicode cSaved = cHere; + // Handle not pre-known separators in switch. + sal_Unicode cSimplified; + if (StringEqualsChar( pFormatter->GetNumThousandSep(), cHere)) + { + cSimplified = ','; + } + else if (StringEqualsChar( pFormatter->GetNumDecimalSep(), cHere)) + { + cSimplified = '.'; + } + else + { + cSimplified = cHere; + } + + OUString& rStr = sStrArray[i]; + + switch ( cSimplified ) + { + case '#': + case '0': + case '?': + if (nThousand > 0) // #... # + { + return nPos; // Error + } + if ( !bDenomin ) + { + nTypeArray[i] = NF_SYMBOLTYPE_DIGIT; + nPos = nPos + rStr.getLength(); + i++; + nCounter++; + while (i < nStringsCnt && + (sStrArray[i][0] == '#' || + sStrArray[i][0] == '0' || + sStrArray[i][0] == '?')) + { + nTypeArray[i] = NF_SYMBOLTYPE_DIGIT; + nPos = nPos + sStrArray[i].getLength(); + nCounter++; + i++; + } + } + else // after denominator, treat any character as text + { + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + nPos = nPos + sStrArray[i].getLength(); + } + break; + case '-': + if ( bDecSep && nDecPos+1 == i && + nTypeArray[nDecPos] == NF_SYMBOLTYPE_DECSEP ) + { + // "0.--" + nTypeArray[i] = NF_SYMBOLTYPE_DIGIT; + nPos = nPos + rStr.getLength(); + i++; + nCounter++; + while (i < nStringsCnt && + (sStrArray[i][0] == '-') ) + { + // If more than two dashes are present in + // currency formats the last dash will be + // interpreted literally as a minus sign. + // Has to be this ugly. Period. + if ( eScannedType == SvNumFormatType::CURRENCY + && rStr.getLength() >= 2 && + (i == nStringsCnt-1 || + sStrArray[i+1][0] != '-') ) + { + break; + } + rStr += sStrArray[i]; + nPos = nPos + sStrArray[i].getLength(); + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + nCounter++; + i++; + } + } + else + { + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + nPos = nPos + sStrArray[i].getLength(); + i++; + } + break; + case '.': + case ',': + case '\'': + case ' ': + if ( StringEqualsChar( sOldThousandSep, cSaved ) ) + { + // previous char with skip empty + sal_Unicode cPre = PreviousChar(i); + sal_Unicode cNext; + if (bExp || bBlank || bFrac) + { + // after E, / or ' ' + if ( !StringEqualsChar( sOldThousandSep, ' ' ) ) + { + nPos = nPos + sStrArray[i].getLength(); + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + i++; // eat it + } + else + { + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + if ( bFrac && (nCounter > 0) ) + bDenomin = true; // end of denominator + } + } + else if (i > 0 && i < nStringsCnt-1 && + (cPre == '#' || cPre == '0' || cPre == '?') && + ((cNext = NextChar(i)) == '#' || cNext == '0' || cNext == '?')) // #,# + { + nPos = nPos + sStrArray[i].getLength(); + if (!bThousand) // only once + { + bThousand = true; + } + // Eat it, will be reinserted at proper grouping positions further down. + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + i++; + } + else if (i > 0 && (cPre == '#' || cPre == '0' || cPre == '?') + && PreviousType(i) == NF_SYMBOLTYPE_DIGIT + && nThousand < FLAG_STANDARD_IN_FORMAT ) + { // #,,,, + if ( StringEqualsChar( sOldThousandSep, ' ' ) ) + { + // strange, those French... + bool bFirst = true; + // set a hard No-Break Space or ConvertMode + const OUString& rSepF = pFormatter->GetNumThousandSep(); + while ( i < nStringsCnt && + sStrArray[i] == sOldThousandSep && + StringEqualsChar( sOldThousandSep, NextChar(i) ) ) + { // last was a space or another space + // is following => separator + nPos = nPos + sStrArray[i].getLength(); + if ( bFirst ) + { + bFirst = false; + rStr = rSepF; + nTypeArray[i] = NF_SYMBOLTYPE_THSEP; + } + else + { + rStr += rSepF; + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + } + nThousand++; + i++; + } + if ( i < nStringsCnt-1 && + sStrArray[i] == sOldThousandSep ) + { + // something following last space + // => space if currency contained, + // else separator + nPos = nPos + sStrArray[i].getLength(); + if ( (nPos <= nCurrPos && + nCurrPos < nPos + sStrArray[i+1].getLength()) || + nTypeArray[i+1] == NF_KEY_CCC || + (i < nStringsCnt-2 && + sStrArray[i+1][0] == '[' && + sStrArray[i+2][0] == '$') ) + { + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + } + else + { + if ( bFirst ) + { + rStr = rSepF; + nTypeArray[i] = NF_SYMBOLTYPE_THSEP; + } + else + { + rStr += rSepF; + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + } + nThousand++; + } + i++; + } + } + else + { + do + { + nThousand++; + nTypeArray[i] = NF_SYMBOLTYPE_THSEP; + nPos = nPos + sStrArray[i].getLength(); + sStrArray[i] = pFormatter->GetNumThousandSep(); + i++; + } + while (i < nStringsCnt && sStrArray[i] == sOldThousandSep); + } + } + else // any grsep + { + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + nPos = nPos + rStr.getLength(); + i++; + while ( i < nStringsCnt && sStrArray[i] == sOldThousandSep ) + { + rStr += sStrArray[i]; + nPos = nPos + sStrArray[i].getLength(); + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + i++; + } + } + } + else if ( StringEqualsChar( sOldDecSep, cSaved ) ) + { + if (bBlank || bFrac) // . behind / or ' ' + { + return nPos; // error + } + else if (bExp) // behind E + { + nPos = nPos + sStrArray[i].getLength(); + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + i++; // eat it + } + else if (bDecSep) // any . + { + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + nPos = nPos + rStr.getLength(); + i++; + while ( i < nStringsCnt && sStrArray[i] == sOldDecSep ) + { + rStr += sStrArray[i]; + nPos = nPos + sStrArray[i].getLength(); + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + i++; + } + } + else + { + nPos = nPos + sStrArray[i].getLength(); + nTypeArray[i] = NF_SYMBOLTYPE_DECSEP; + sStrArray[i] = pFormatter->GetNumDecimalSep(); + bDecSep = true; + nDecPos = i; + nCntPre = nCounter; + nCounter = 0; + + i++; + } + } // of else = DecSep + else // . without meaning + { + if (cSaved == ' ' && + eScannedType == SvNumFormatType::FRACTION && + StringEqualsChar( sStrArray[i], ' ' ) ) + { + if (!bBlank && !bFrac) // no dups + { // or behind / + if (bDecSep && nCounter > 0) // dec. + { + return nPos; // error + } + bBlank = true; + nBlankPos = i; + nCntPre = nCounter; + nCounter = 0; + } + if ( bFrac && (nCounter > 0) ) + bDenomin = true; // next content is not part of denominator + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + nPos = nPos + sStrArray[i].getLength(); + } + else + { + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + if ( bFrac && (nCounter > 0) ) + bDenomin = true; // next content is not part of denominator + nPos = nPos + rStr.getLength(); + i++; + while (i < nStringsCnt && StringEqualsChar( sStrArray[i], cSaved ) ) + { + rStr += sStrArray[i]; + nPos = nPos + sStrArray[i].getLength(); + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + i++; + } + } + } + break; + case '/': + if (eScannedType == SvNumFormatType::FRACTION) + { + if ( i == 0 || + (nTypeArray[i-1] != NF_SYMBOLTYPE_DIGIT && + nTypeArray[i-1] != NF_SYMBOLTYPE_EMPTY) ) + { + return nPos ? nPos : 1; // /? not allowed + } + else if (!bFrac || (bDecSep && nCounter > 0)) + { + bFrac = true; + nCntPost = nCounter; + nCounter = 0; + nTypeArray[i] = NF_SYMBOLTYPE_FRAC; + nPos = nPos + sStrArray[i].getLength(); + i++; + } + else // / double or in , in the denominator + { + return nPos; // Error + } + } + else + { + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + nPos = nPos + sStrArray[i].getLength(); + i++; + } + break; + case '[' : + if ( eScannedType == SvNumFormatType::CURRENCY && + i < nStringsCnt-1 && + nTypeArray[i+1] == NF_SYMBOLTYPE_STRING && + sStrArray[i+1][0] == '$' ) + { + // [$DM-xxx] + nPos = nPos + sStrArray[i].getLength(); // [ + nTypeArray[i] = NF_SYMBOLTYPE_CURRDEL; + nPos = nPos + sStrArray[++i].getLength(); // $ + sStrArray[i-1] += sStrArray[i]; // [$ + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + if ( ++i >= nStringsCnt ) + { + return nPos; // Error + } + nPos = nPos + sStrArray[i].getLength(); // DM + OUString* pStr = &sStrArray[i]; + nTypeArray[i] = NF_SYMBOLTYPE_CURRENCY; // convert + bool bHadDash = false; + i++; + while ( i < nStringsCnt && sStrArray[i][0] != ']' ) + { + nPos = nPos + sStrArray[i].getLength(); + if ( bHadDash ) + { + *pStr += sStrArray[i]; + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + } + else + { + if ( sStrArray[i][0] == '-' ) + { + bHadDash = true; + pStr = &sStrArray[i]; + nTypeArray[i] = NF_SYMBOLTYPE_CURREXT; + } + else + { + *pStr += sStrArray[i]; + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + } + } + i++; + } + if ( rStr.getLength() && i < nStringsCnt && sStrArray[i][0] == ']' ) + { + nTypeArray[i] = NF_SYMBOLTYPE_CURRDEL; + nPos = nPos + sStrArray[i].getLength(); + i++; + } + else + { + return nPos; // Error + } + } + else + { + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + nPos = nPos + sStrArray[i].getLength(); + i++; + } + break; + default: // Other Dels + if (eScannedType == SvNumFormatType::PERCENT && cHere == '%') + { + nTypeArray[i] = NF_SYMBOLTYPE_PERCENT; + } + else + { + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + } + nPos = nPos + sStrArray[i].getLength(); + i++; + break; + } // of switch (Del) + } // of else Del + else + { + SAL_WARN( "svl.numbers", "unknown NF_SYMBOLTYPE_..." ); + nPos = nPos + sStrArray[i].getLength(); + i++; + } + } // of while + if (eScannedType == SvNumFormatType::FRACTION) + { + if (bFrac) + { + nCntExp = nCounter; + } + else if (bBlank) + { + nCntPost = nCounter; + } + else + { + nCntPre = nCounter; + } + } + else + { + if (bExp) + { + nCntExp = nCounter; + } + else if (bDecSep) + { + nCntPost = nCounter; + } + else + { + nCntPre = nCounter; + } + } + if (bThousand) // Expansion of grouping separators + { + sal_uInt16 nMaxPos; + if (bFrac) + { + if (bBlank) + { + nMaxPos = nBlankPos; + } + else + { + nMaxPos = 0; // no grouping + } + } + else if (bDecSep) // decimal separator present + { + nMaxPos = nDecPos; + } + else if (bExp) // 'E' exponent present + { + nMaxPos = nExpPos; + } + else // up to end + { + nMaxPos = i; + } + // Insert separators at proper positions. + sal_Int32 nCount = 0; + utl::DigitGroupingIterator aGrouping( pLoc->getDigitGrouping()); + size_t nFirstDigitSymbol = nMaxPos; + size_t nFirstGroupingSymbol = nMaxPos; + i = nMaxPos; + while (i-- > 0) + { + if (nTypeArray[i] == NF_SYMBOLTYPE_DIGIT) + { + nFirstDigitSymbol = i; + nCount = nCount + sStrArray[i].getLength(); // MSC converts += to int and then warns, so ... + // Insert separator only if not leftmost symbol. + if (i > 0 && nCount >= aGrouping.getPos()) + { + DBG_ASSERT( sStrArray[i].getLength() == 1, + "ImpSvNumberformatScan::FinalScan: combined digits in group separator insertion"); + if (!InsertSymbol( i, NF_SYMBOLTYPE_THSEP, pFormatter->GetNumThousandSep())) + { + // nPos isn't correct here, but signals error + return nPos; + } + // i may have been decremented by 1 + nFirstDigitSymbol = i + 1; + nFirstGroupingSymbol = i; + aGrouping.advance(); + } + } + } + // Generated something like "string",000; remove separator again. + if (nFirstGroupingSymbol < nFirstDigitSymbol) + { + nTypeArray[nFirstGroupingSymbol] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + } + } + // Combine digits into groups to save memory (Info will be copied + // later, taking only non-empty symbols). + for (i = 0; i < nStringsCnt; ++i) + { + if (nTypeArray[i] == NF_SYMBOLTYPE_DIGIT) + { + OUString& rStr = sStrArray[i]; + while (++i < nStringsCnt && nTypeArray[i] == NF_SYMBOLTYPE_DIGIT) + { + rStr += sStrArray[i]; + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + } + } + } + break; // of SvNumFormatType::NUMBER + case SvNumFormatType::DATE: + while (i < nStringsCnt) + { + switch (nTypeArray[i]) + { + case NF_SYMBOLTYPE_BLANK: + case NF_SYMBOLTYPE_STAR: + case NF_SYMBOLTYPE_STRING: + nPos = nPos + sStrArray[i].getLength(); + i++; + break; + case NF_SYMBOLTYPE_DEL: + int nCalRet; + if (sStrArray[i] == sOldDateSep) + { + nTypeArray[i] = NF_SYMBOLTYPE_DATESEP; + nPos = nPos + sStrArray[i].getLength(); + if (bConvertMode) + { + sStrArray[i] = pFormatter->GetDateSep(); + } + i++; + } + else if ( (nCalRet = FinalScanGetCalendar( nPos, i, nResultStringsCnt )) != 0 ) + { + if ( nCalRet < 0 ) + { + return nPos; // error + } + } + else + { + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + nPos = nPos + sStrArray[i].getLength(); + i++; + } + break; + case NF_KEY_THAI_T : + bThaiT = true; + [[fallthrough]]; + case NF_KEY_M: // M + case NF_KEY_MM: // MM + case NF_KEY_MMM: // MMM + case NF_KEY_MMMM: // MMMM + case NF_KEY_MMMMM: // MMMMM + case NF_KEY_Q: // Q + case NF_KEY_QQ: // QQ + case NF_KEY_D: // D + case NF_KEY_DD: // DD + case NF_KEY_DDD: // DDD + case NF_KEY_DDDD: // DDDD + case NF_KEY_YY: // YY + case NF_KEY_YYYY: // YYYY + case NF_KEY_NN: // NN + case NF_KEY_NNN: // NNN + case NF_KEY_NNNN: // NNNN + case NF_KEY_WW : // WW + case NF_KEY_AAA : // AAA + case NF_KEY_AAAA : // AAAA + case NF_KEY_EC : // E + case NF_KEY_EEC : // EE + case NF_KEY_G : // G + case NF_KEY_GG : // GG + case NF_KEY_GGG : // GGG + case NF_KEY_R : // R + case NF_KEY_RR : // RR + sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT + nPos = nPos + sStrArray[i].getLength(); + if (bNewDateOrder) + { + // For simple numeric date formats record date order and + // later rearrange. + switch (nTypeArray[i]) + { + case NF_KEY_M: + case NF_KEY_MM: + if (nMonthPos == SAL_MAX_UINT16) + nMonthPos = i; + else + bNewDateOrder = false; + break; + case NF_KEY_D: + case NF_KEY_DD: + if (nDayPos == SAL_MAX_UINT16) + nDayPos = i; + else + bNewDateOrder = false; + break; + case NF_KEY_YY: + case NF_KEY_YYYY: + if (nYearPos == SAL_MAX_UINT16) + nYearPos = i; + else + bNewDateOrder = false; + break; + default: + ; // nothing + } + } + i++; + break; + default: // Other keywords + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + nPos = nPos + sStrArray[i].getLength(); + i++; + break; + } + } // of while + break; // of SvNumFormatType::DATE + case SvNumFormatType::TIME: + while (i < nStringsCnt) + { + sal_Unicode cChar; + + switch (nTypeArray[i]) + { + case NF_SYMBOLTYPE_BLANK: + case NF_SYMBOLTYPE_STAR: + nPos = nPos + sStrArray[i].getLength(); + i++; + break; + case NF_SYMBOLTYPE_DEL: + switch( sStrArray[i][0] ) + { + case '0': + if ( Is100SecZero( i, bDecSep ) ) + { + bDecSep = true; + nTypeArray[i] = NF_SYMBOLTYPE_DIGIT; + OUString& rStr = sStrArray[i]; + nCounter++; + i++; + while (i < nStringsCnt && + sStrArray[i][0] == '0') + { + rStr += sStrArray[i]; + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + nCounter++; + i++; + } + nPos += rStr.getLength(); + } + else + { + return nPos; + } + break; + case '#': + case '?': + return nPos; + case '[': + if (bThousand) // Double + { + return nPos; + } + bThousand = true; // Empty for Time + cChar = pChrCls->uppercase(OUString(NextChar(i)))[0]; + if ( cChar == cOldKeyH ) + { + nThousand = 1; // H + } + else if ( cChar == cOldKeyMI ) + { + nThousand = 2; // M + } + else if ( cChar == cOldKeyS ) + { + nThousand = 3; // S + } + else + { + return nPos; + } + nPos = nPos + sStrArray[i].getLength(); + i++; + break; + case ']': + if (!bThousand) // No preceding [ + { + return nPos; + } + nPos = nPos + sStrArray[i].getLength(); + i++; + break; + default: + nPos = nPos + sStrArray[i].getLength(); + if ( sStrArray[i] == sOldTimeSep ) + { + nTypeArray[i] = NF_SYMBOLTYPE_TIMESEP; + if ( bConvertMode ) + { + sStrArray[i] = pLoc->getTimeSep(); + } + } + else if ( sStrArray[i] == sOldTime100SecSep ) + { + bDecSep = true; + nTypeArray[i] = NF_SYMBOLTYPE_TIME100SECSEP; + if ( bConvertMode ) + { + sStrArray[i] = pLoc->getTime100SecSep(); + } + } + else + { + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + } + i++; + break; + } + break; + case NF_SYMBOLTYPE_STRING: + nPos = nPos + sStrArray[i].getLength(); + i++; + break; + case NF_KEY_AMPM: // AM/PM + case NF_KEY_AP: // A/P + bExp = true; // Abuse for A/P + sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT + nPos = nPos + sStrArray[i].getLength(); + i++; + break; + case NF_KEY_THAI_T : + bThaiT = true; + [[fallthrough]]; + case NF_KEY_MI: // M + case NF_KEY_MMI: // MM + case NF_KEY_H: // H + case NF_KEY_HH: // HH + case NF_KEY_S: // S + case NF_KEY_SS: // SS + sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT + nPos = nPos + sStrArray[i].getLength(); + i++; + break; + default: // Other keywords + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + nPos = nPos + sStrArray[i].getLength(); + i++; + break; + } + } // of while + nCntPost = nCounter; // Zero counter + if (bExp) + { + nCntExp = 1; // Remembers AM/PM + } + break; // of SvNumFormatType::TIME + case SvNumFormatType::DATETIME: + while (i < nStringsCnt) + { + int nCalRet; + switch (nTypeArray[i]) + { + case NF_SYMBOLTYPE_BLANK: + case NF_SYMBOLTYPE_STAR: + case NF_SYMBOLTYPE_STRING: + nPos = nPos + sStrArray[i].getLength(); + i++; + break; + case NF_SYMBOLTYPE_DEL: + if ( (nCalRet = FinalScanGetCalendar( nPos, i, nResultStringsCnt )) != 0 ) + { + if ( nCalRet < 0 ) + { + return nPos; // Error + } + } + else + { + switch( sStrArray[i][0] ) + { + case '0': + if (bTimePart && Is100SecZero(i, bDecSep) && nCounter < MaxCntPost) + { + bDecSep = true; + nTypeArray[i] = NF_SYMBOLTYPE_DIGIT; + OUString& rStr = sStrArray[i]; + nCounter++; + i++; + while (i < nStringsCnt && + sStrArray[i][0] == '0' && nCounter < MaxCntPost) + { + rStr += sStrArray[i]; + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + nCounter++; + i++; + } + nPos += rStr.getLength(); + } + else + { + return nPos; + } + break; + case '#': + case '?': + return nPos; + default: + nPos = nPos + sStrArray[i].getLength(); + if (bTimePart) + { + if ( sStrArray[i] == sOldTimeSep ) + { + nTypeArray[i] = NF_SYMBOLTYPE_TIMESEP; + if ( bConvertMode ) + { + sStrArray[i] = pLoc->getTimeSep(); + } + } + else if ( sStrArray[i] == sOldTime100SecSep ) + { + bDecSep = true; + nTypeArray[i] = NF_SYMBOLTYPE_TIME100SECSEP; + if ( bConvertMode ) + { + sStrArray[i] = pLoc->getTime100SecSep(); + } + } + else + { + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + } + } + else + { + if ( sStrArray[i] == sOldDateSep ) + { + nTypeArray[i] = NF_SYMBOLTYPE_DATESEP; + if (bConvertMode) + sStrArray[i] = pFormatter->GetDateSep(); + } + else + { + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + } + } + i++; + break; + } + } + break; + case NF_KEY_AMPM: // AM/PM + case NF_KEY_AP: // A/P + bTimePart = true; + bExp = true; // Abuse for A/P + sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT + nPos = nPos + sStrArray[i].getLength(); + i++; + break; + case NF_KEY_MI: // M + case NF_KEY_MMI: // MM + case NF_KEY_H: // H + case NF_KEY_HH: // HH + case NF_KEY_S: // S + case NF_KEY_SS: // SS + bTimePart = true; + sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT + nPos = nPos + sStrArray[i].getLength(); + i++; + break; + case NF_KEY_M: // M + case NF_KEY_MM: // MM + case NF_KEY_MMM: // MMM + case NF_KEY_MMMM: // MMMM + case NF_KEY_MMMMM: // MMMMM + case NF_KEY_Q: // Q + case NF_KEY_QQ: // QQ + case NF_KEY_D: // D + case NF_KEY_DD: // DD + case NF_KEY_DDD: // DDD + case NF_KEY_DDDD: // DDDD + case NF_KEY_YY: // YY + case NF_KEY_YYYY: // YYYY + case NF_KEY_NN: // NN + case NF_KEY_NNN: // NNN + case NF_KEY_NNNN: // NNNN + case NF_KEY_WW : // WW + case NF_KEY_AAA : // AAA + case NF_KEY_AAAA : // AAAA + case NF_KEY_EC : // E + case NF_KEY_EEC : // EE + case NF_KEY_G : // G + case NF_KEY_GG : // GG + case NF_KEY_GGG : // GGG + case NF_KEY_R : // R + case NF_KEY_RR : // RR + bTimePart = false; + sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT + nPos = nPos + sStrArray[i].getLength(); + if (bNewDateOrder) + { + // For simple numeric date formats record date order and + // later rearrange. + switch (nTypeArray[i]) + { + case NF_KEY_M: + case NF_KEY_MM: + if (nMonthPos == SAL_MAX_UINT16) + nMonthPos = i; + else + bNewDateOrder = false; + break; + case NF_KEY_D: + case NF_KEY_DD: + if (nDayPos == SAL_MAX_UINT16) + nDayPos = i; + else + bNewDateOrder = false; + break; + case NF_KEY_YY: + case NF_KEY_YYYY: + if (nYearPos == SAL_MAX_UINT16) + nYearPos = i; + else + bNewDateOrder = false; + break; + default: + ; // nothing + } + } + i++; + break; + case NF_KEY_THAI_T : + bThaiT = true; + sStrArray[i] = sKeyword[nTypeArray[i]]; + nPos = nPos + sStrArray[i].getLength(); + i++; + break; + default: // Other keywords + nTypeArray[i] = NF_SYMBOLTYPE_STRING; + nPos = nPos + sStrArray[i].getLength(); + i++; + break; + } + } // of while + nCntPost = nCounter; // decimals (100th seconds) + if (bExp) + { + nCntExp = 1; // Remembers AM/PM + } + break; // of SvNumFormatType::DATETIME + default: + break; + } + if (eScannedType == SvNumFormatType::SCIENTIFIC && + (nCntPre + nCntPost == 0 || nCntExp == 0)) + { + return nPos; + } + else if (eScannedType == SvNumFormatType::FRACTION && (nCntExp > 8 || nCntExp == 0)) + { + return nPos; + } + if (bThaiT && !GetNatNumModifier()) + { + SetNatNumModifier(1); + } + if ( bConvertMode ) + { + if (bNewDateOrder && sOldDateSep == "-") + { + // Keep ISO formats Y-M-D, Y-M and M-D + if (IsDateFragment( nYearPos, nMonthPos)) + { + nTypeArray[nYearPos+1] = NF_SYMBOLTYPE_STRING; + sStrArray[nYearPos+1] = sOldDateSep; + bNewDateOrder = false; + } + if (IsDateFragment( nMonthPos, nDayPos)) + { + nTypeArray[nMonthPos+1] = NF_SYMBOLTYPE_STRING; + sStrArray[nMonthPos+1] = sOldDateSep; + bNewDateOrder = false; + } + } + if (bNewDateOrder) + { + // Rearrange date order to the target locale if the original order + // includes date separators and is adjacent. + /* TODO: for incomplete dates trailing separators need to be + * handled according to the locale's usage, e.g. en-US M/D should + * be converted to de-DE D.M. and vice versa. As is, it's + * M/D -> D.M and D.M. -> M/D/ where specifically the latter looks + * odd. Check accepted date patterns and append/remove? */ + switch (eOldDateOrder) + { + case DateOrder::DMY: + switch (pLoc->getDateOrder()) + { + case DateOrder::MDY: + // Convert only if the actual format is not of YDM + // order (which would be a completely unusual order + // anyway, but..), e.g. YYYY.DD.MM not to + // YYYY/MM/DD + if (IsDateFragment( nDayPos, nMonthPos) && !IsDateFragment( nYearPos, nDayPos)) + SwapArrayElements( nDayPos, nMonthPos); + break; + case DateOrder::YMD: + if (nYearPos != SAL_MAX_UINT16) + { + if (IsDateFragment( nDayPos, nMonthPos) && IsDateFragment( nMonthPos, nYearPos)) + SwapArrayElements( nDayPos, nYearPos); + } + else + { + if (IsDateFragment( nDayPos, nMonthPos)) + SwapArrayElements( nDayPos, nMonthPos); + } + break; + default: + ; // nothing + } + break; + case DateOrder::MDY: + switch (pLoc->getDateOrder()) + { + case DateOrder::DMY: + // Convert only if the actual format is not of YMD + // order, e.g. YYYY/MM/DD not to YYYY.DD.MM + /* TODO: convert such to DD.MM.YYYY instead? */ + if (IsDateFragment( nMonthPos, nDayPos) && !IsDateFragment( nYearPos, nMonthPos)) + SwapArrayElements( nMonthPos, nDayPos); + break; + case DateOrder::YMD: + if (nYearPos != SAL_MAX_UINT16) + { + if (IsDateFragment( nMonthPos, nDayPos) && IsDateFragment( nDayPos, nYearPos)) + { + SwapArrayElements( nYearPos, nMonthPos); // YDM + SwapArrayElements( nYearPos, nDayPos); // YMD + } + } + break; + default: + ; // nothing + } + break; + case DateOrder::YMD: + switch (pLoc->getDateOrder()) + { + case DateOrder::DMY: + if (nYearPos != SAL_MAX_UINT16) + { + if (IsDateFragment( nYearPos, nMonthPos) && IsDateFragment( nMonthPos, nDayPos)) + SwapArrayElements( nYearPos, nDayPos); + } + else + { + if (IsDateFragment( nMonthPos, nDayPos)) + SwapArrayElements( nMonthPos, nDayPos); + } + break; + case DateOrder::MDY: + if (nYearPos != SAL_MAX_UINT16) + { + if (IsDateFragment( nYearPos, nMonthPos) && IsDateFragment( nMonthPos, nDayPos)) + { + SwapArrayElements( nYearPos, nDayPos); // DMY + SwapArrayElements( nYearPos, nMonthPos); // MDY + } + } + break; + default: + ; // nothing + } + break; + default: + ; // nothing + } + } + // strings containing keywords of the target locale must be quoted, so + // the user sees the difference and is able to edit the format string + for ( i=0; i < nStringsCnt; i++ ) + { + if ( nTypeArray[i] == NF_SYMBOLTYPE_STRING && + sStrArray[i][0] != '\"' ) + { + if ( bConvertSystemToSystem && eScannedType == SvNumFormatType::CURRENCY ) + { + // don't stringize automatic currency, will be converted + if ( sStrArray[i] == sOldCurSymbol ) + { + continue; // for + } + // DM might be split into D and M + if ( sStrArray[i].getLength() < sOldCurSymbol.getLength() && + pChrCls->uppercase( sStrArray[i], 0, 1 )[0] == + sOldCurString[0] ) + { + OUString aTmp( sStrArray[i] ); + sal_uInt16 j = i + 1; + while ( aTmp.getLength() < sOldCurSymbol.getLength() && + j < nStringsCnt && + nTypeArray[j] == NF_SYMBOLTYPE_STRING ) + { + aTmp += sStrArray[j++]; + } + if ( pChrCls->uppercase( aTmp ) == sOldCurString ) + { + sStrArray[i++] = aTmp; + for ( ; iGetNumThousandSep(), c) || + StringEqualsChar( pFormatter->GetNumDecimalSep(), c) || + (c == ' ' && + (StringEqualsChar( pFormatter->GetNumThousandSep(), cNoBreakSpace) || + StringEqualsChar( pFormatter->GetNumThousandSep(), cNarrowNoBreakSpace))))) + { + rString += sStrArray[i]; + } + else if ((eScannedType & SvNumFormatType::DATE) && + StringEqualsChar( pFormatter->GetDateSep(), c)) + { + rString += sStrArray[i]; + } + else if ((eScannedType & SvNumFormatType::TIME) && + (StringEqualsChar( pLoc->getTimeSep(), c) || + StringEqualsChar( pLoc->getTime100SecSep(), c))) + { + rString += sStrArray[i]; + } + else if (eScannedType & SvNumFormatType::FRACTION) + { + rString += sStrArray[i]; + } + else + { + rString += OUStringChar(c); + } + break; + default: + rString += sStrArray[i]; + } + } + else + { + rString += sStrArray[i]; + } + if ( RemoveQuotes( sStrArray[i] ) > 0 ) + { + // update currency up to quoted string + if ( eScannedType == SvNumFormatType::CURRENCY ) + { + // dM -> DM or DM -> $ in old automatic + // currency formats, oh my ..., why did we ever introduce them? + OUString aTmp( pChrCls->uppercase( sStrArray[iPos], nArrPos, + sStrArray[iPos].getLength()-nArrPos ) ); + sal_Int32 nCPos = aTmp.indexOf( sOldCurString ); + if ( nCPos >= 0 ) + { + const OUString& rCur = bConvertMode && bConvertSystemToSystem ? + GetCurSymbol() : sOldCurSymbol; + sStrArray[iPos] = sStrArray[iPos].replaceAt( nArrPos + nCPos, + sOldCurString.getLength(), + rCur ); + rString = rString.replaceAt( nStringPos + nCPos, + sOldCurString.getLength(), + rCur ); + } + nStringPos = rString.getLength(); + if ( iPos == i ) + { + nArrPos = sStrArray[iPos].getLength(); + } + else + { + nArrPos = sStrArray[iPos].getLength() + sStrArray[i].getLength(); + } + } + } + if ( iPos != i ) + { + sStrArray[iPos] += sStrArray[i]; + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + } + i++; + } + while ( i < nStringsCnt && nTypeArray[i] == NF_SYMBOLTYPE_STRING ); + + if ( i < nStringsCnt ) + { + i--; // enter switch on next symbol again + } + if ( eScannedType == SvNumFormatType::CURRENCY && nStringPos < rString.getLength() ) + { + // same as above, since last RemoveQuotes + OUString aTmp( pChrCls->uppercase( sStrArray[iPos], nArrPos, + sStrArray[iPos].getLength()-nArrPos ) ); + sal_Int32 nCPos = aTmp.indexOf( sOldCurString ); + if ( nCPos >= 0 ) + { + const OUString& rCur = bConvertMode && bConvertSystemToSystem ? + GetCurSymbol() : sOldCurSymbol; + sStrArray[iPos] = sStrArray[iPos].replaceAt( nArrPos + nCPos, + sOldCurString.getLength(), + rCur ); + rString = rString.replaceAt( nStringPos + nCPos, + sOldCurString.getLength(), rCur ); + } + } + break; + case NF_SYMBOLTYPE_CURRENCY : + rString += sStrArray[i]; + RemoveQuotes( sStrArray[i] ); + break; + case NF_KEY_THAI_T: + if (bThaiT && GetNatNumModifier() == 1) + { + // Remove T from format code, will be replaced with a [NatNum1] prefix. + nTypeArray[i] = NF_SYMBOLTYPE_EMPTY; + nResultStringsCnt--; + } + else + { + rString += sStrArray[i]; + } + break; + case NF_SYMBOLTYPE_EMPTY : + // nothing + break; + default: + rString += sStrArray[i]; + } + i++; + } + return 0; +} + +sal_Int32 ImpSvNumberformatScan::RemoveQuotes( OUString& rStr ) +{ + if ( rStr.getLength() > 1 ) + { + sal_Unicode c = rStr[0]; + sal_Int32 n = rStr.getLength() - 1; + if ( c == '"' && rStr[n] == '"' ) + { + rStr = rStr.copy( 1, n-1); + return 2; + } + else if ( c == '\\' ) + { + rStr = rStr.copy(1); + return 1; + } + } + return 0; +} + +sal_Int32 ImpSvNumberformatScan::ScanFormat( OUString& rString ) +{ + sal_Int32 res = Symbol_Division(rString); // Lexical analysis + if (!res) + { + res = ScanType(); // Recognizing the Format type + } + if (!res) + { + res = FinalScan( rString ); // Type dependent final analysis + } + return res; // res = control position; res = 0 => Format ok +} + +void ImpSvNumberformatScan::CopyInfo(ImpSvNumberformatInfo* pInfo, sal_uInt16 nCnt) +{ + size_t i,j; + j = 0; + i = 0; + while (i < nCnt && j < NF_MAX_FORMAT_SYMBOLS) + { + if (nTypeArray[j] != NF_SYMBOLTYPE_EMPTY) + { + pInfo->sStrArray[i] = sStrArray[j]; + pInfo->nTypeArray[i] = nTypeArray[j]; + i++; + } + j++; + } + pInfo->eScannedType = eScannedType; + pInfo->bThousand = bThousand; + pInfo->nThousand = nThousand; + pInfo->nCntPre = nCntPre; + pInfo->nCntPost = nCntPost; + pInfo->nCntExp = nCntExp; +} + +bool ImpSvNumberformatScan::ReplaceBooleanEquivalent( OUString& rString ) +{ + InitKeywords(); + /* TODO: compare case insensitive? Or rather leave as is and case not + * matching indicates user supplied on purpose? Written to file / generated + * was always uppercase. */ + if (rString == sBooleanEquivalent1 || rString == sBooleanEquivalent2) + { + rString = GetKeywords()[NF_KEY_BOOLEAN]; + return true; + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/numbers/zforscan.hxx b/svl/source/numbers/zforscan.hxx new file mode 100644 index 000000000..4e6f7d145 --- /dev/null +++ b/svl/source/numbers/zforscan.hxx @@ -0,0 +1,304 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SVL_SOURCE_NUMBERS_ZFORSCAN_HXX +#define INCLUDED_SVL_SOURCE_NUMBERS_ZFORSCAN_HXX + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class SvNumberFormatter; +struct ImpSvNumberformatInfo; + + +const size_t NF_MAX_DEFAULT_COLORS = 10; + +// Hack: nThousand==1000 => "Default" occurs in format string +const sal_uInt16 FLAG_STANDARD_IN_FORMAT = 1000; + +class ImpSvNumberformatScan +{ +public: + + /** Specify what keyword localization is allowed when scanning the format code. */ + enum class KeywordLocalization + { + LocaleLegacy, ///< unfortunately localized in few locales, otherwise English + EnglishOnly, ///< only English, no localized keywords + AllowEnglish ///< allow English keywords as well as localized keywords + }; + + explicit ImpSvNumberformatScan( SvNumberFormatter* pFormatter ); + ~ImpSvNumberformatScan(); + void ChangeIntl( KeywordLocalization eKeywordLocalization = KeywordLocalization::AllowEnglish ); // Replaces Keywords + + void ChangeNullDate(sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear); // Replaces reference date + void ChangeStandardPrec(sal_uInt16 nPrec); // Replaces standard precision + + sal_Int32 ScanFormat( OUString& rString ); // Call scan analysis + + void CopyInfo(ImpSvNumberformatInfo* pInfo, + sal_uInt16 nCnt); // Copies the FormatInfo + sal_uInt16 GetResultStringsCnt() const { return nResultStringsCnt; } + + const CharClass& GetChrCls() const { return *pFormatter->GetCharClass(); } + const LocaleDataWrapper& GetLoc() const { return *pFormatter->GetLocaleData(); } + CalendarWrapper& GetCal() const { return *pFormatter->GetCalendar(); } + + const NfKeywordTable & GetKeywords() const + { + if ( bKeywordsNeedInit ) + { + InitKeywords(); + } + return sKeyword; + } + + static const NfKeywordTable & GetEnglishKeywords() + { + return sEnglishKeyword; + } + + // Keywords used in output like true and false + const OUString& GetSpecialKeyword( NfKeywordIndex eIdx ) const + { + if ( sKeyword[eIdx].isEmpty() ) + { + InitSpecialKeyword( eIdx ); + } + return sKeyword[eIdx]; + } + const OUString& GetTrueString() const { return GetSpecialKeyword( NF_KEY_TRUE ); } + const OUString& GetFalseString() const { return GetSpecialKeyword( NF_KEY_FALSE ); } + const OUString& GetRedString() const { return GetKeywords()[NF_KEY_RED]; } + const OUString& GetBooleanString() const { return GetKeywords()[NF_KEY_BOOLEAN]; } + static const ::std::vector & GetStandardColors() + { + return StandardColor; + } + static size_t GetMaxDefaultColors() + { + return NF_MAX_DEFAULT_COLORS; + } + + const Date& GetNullDate() const { return maNullDate; } + const OUString& GetStandardName() const + { + if ( bKeywordsNeedInit ) + { + InitKeywords(); + } + return sNameStandardFormat; + } + sal_uInt16 GetStandardPrec() const { return nStandardPrec; } + static const Color& GetRedColor() { return StandardColor[4]; } + const Color* GetColor(OUString& sStr) const; // Set main colors or defines colors + + // the compatibility currency symbol for old automatic currency formats + const OUString& GetCurSymbol() const + { + if ( bCompatCurNeedInit ) + { + InitCompatCur(); + } + return sCurSymbol; + } + + // the compatibility currency abbreviation for CCC format code + const OUString& GetCurAbbrev() const + { + if ( bCompatCurNeedInit ) + { + InitCompatCur(); + } + return sCurAbbrev; + } + + // the compatibility currency symbol upper case for old automatic currency formats + const OUString& GetCurString() const + { + if ( bCompatCurNeedInit ) + { + InitCompatCur(); + } + return sCurString; + } + + /// Replace Boolean equivalent format codes with proper Boolean format. + bool ReplaceBooleanEquivalent( OUString& rString ); + + void SetConvertMode(LanguageType eTmpLge, LanguageType eNewLge, + bool bSystemToSystem, bool bConvertDateOrder) + { + bConvertMode = true; + eNewLnge = eNewLge; + eTmpLnge = eTmpLge; + bConvertSystemToSystem = bSystemToSystem; + mbConvertDateOrder = bConvertDateOrder; + } + // Only changes the bool variable, in order to temporarily pause the convert mode + void SetConvertMode(bool bMode) { bConvertMode = bMode; } + bool GetConvertMode() const { return bConvertMode; } + LanguageType GetNewLnge() const { return eNewLnge; } // Read access on ConvertMode and convert country/language + LanguageType GetTmpLnge() const { return eTmpLnge; } // Read access on StartCountry/Language + void SetNewLnge( LanguageType e ) { eNewLnge = e; } // Set new convert country/language + + /// get Thai T speciality + sal_uInt8 GetNatNumModifier() const { return nNatNumModifier; } + /// set Thai T speciality + void SetNatNumModifier( sal_uInt8 n ) { nNatNumModifier = n; } + + SvNumberFormatter* GetNumberformatter() { return pFormatter; } // Access to formatter (for zformat.cxx) + + /// Get type scanned (so far). + SvNumFormatType GetScannedType() const { return eScannedType; } + + static constexpr OUStringLiteral sErrStr = u"#FMT"; // String for error output + +private: // Private section + NfKeywordTable sKeyword; // Syntax keywords + static const NfKeywordTable sEnglishKeyword; // English Syntax keywords + static const ::std::vector StandardColor; // Standard color array + Date maNullDate; // 30Dec1899 + OUString sNameStandardFormat; // "Standard" + sal_uInt16 nStandardPrec; // Default Precision for Standardformat + SvNumberFormatter* pFormatter; // Pointer to the FormatList + css::uno::Reference< css::i18n::XNumberFormatCode > xNFC; + + OUString sStrArray[NF_MAX_FORMAT_SYMBOLS]; // Array of symbols + short nTypeArray[NF_MAX_FORMAT_SYMBOLS]; // Array of infos + // External Infos: + sal_uInt16 nResultStringsCnt; // Result symbol count + SvNumFormatType eScannedType; // Type according to scan + bool bThousand; // With thousands marker + sal_uInt16 nThousand; // Counts ... series + sal_uInt16 nCntPre; // Counts digits of integral part + sal_uInt16 nCntPost; // Counts digits of fractional part + sal_uInt16 nCntExp; // Counts exponent digits AM/PM + // Internal info: + sal_uInt16 nStringsCnt; // Symbol count + sal_uInt16 nExpPos; // Internal position of E + sal_uInt16 nBlankPos; // Internal position of the Blank + short nDecPos; // Internal position of the , + bool bExp; // Set when reading E + bool bFrac; // Set when reading / + bool bBlank; // Set when reading ' ' (Fraction) + bool bDecSep; // Set on first , + mutable bool bKeywordsNeedInit; // Locale dependent keywords need to be initialized + mutable bool bCompatCurNeedInit; // Locale dependent compatibility currency need to be initialized + OUString sCurSymbol; // Currency symbol for compatibility format codes + OUString sCurString; // Currency symbol in upper case + OUString sCurAbbrev; // Currency abbreviation + OUString sBooleanEquivalent1; // "TRUE";"TRUE";"FALSE" + OUString sBooleanEquivalent2; // [>0]"TRUE";[<0]"TRUE";"FALSE" + + bool bConvertMode; // Set in the convert mode + bool mbConvertDateOrder; // Set in the convert mode whether to convert date particles order + + LanguageType eNewLnge; // Language/country which the scanned string is converted to (for Excel filter) + LanguageType eTmpLnge; // Language/country which the scanned string is converted from (for Excel filter) + + bool bConvertSystemToSystem; // Whether the conversion is from one system locale to another system locale + // (in this case the automatic currency symbol is converted too). + + sal_Int32 nCurrPos; // Position of currency symbol + + sal_uInt8 nNatNumModifier; // Thai T speciality + + KeywordLocalization meKeywordLocalization; ///< which keywords localization to scan + + // Copy assignment is forbidden and not implemented. + ImpSvNumberformatScan (const ImpSvNumberformatScan &) = delete; + ImpSvNumberformatScan & operator= (const ImpSvNumberformatScan &) = delete; + + void InitKeywords() const; + void InitSpecialKeyword( NfKeywordIndex eIdx ) const; + void InitCompatCur() const; + + void SetDependentKeywords(); + // Sets the language dependent keywords + void SkipStrings(sal_uInt16& i, sal_Int32& nPos) const;// Skips StringSymbols + sal_uInt16 PreviousKeyword(sal_uInt16 i) const; // Returns index of the preceding one + // Keyword or 0 + sal_uInt16 NextKeyword(sal_uInt16 i) const; // Returns index of the next one + // Keyword or 0 + sal_Unicode PreviousChar(sal_uInt16 i) const; // Returns last char before index skips EMPTY, STRING, STAR, BLANK + sal_Unicode NextChar(sal_uInt16 i) const; // Returns first following char + short PreviousType( sal_uInt16 i ) const; // Returns type before position skips EMPTY + bool IsLastBlankBeforeFrac(sal_uInt16 i) const; // True <=> there won't be a ' ' until the '/' + void Reset(); // Reset all variables before starting the analysis + + /** Determine keyword at nPos. + @param rbFoundEnglish set if English instead of locale's keyword + found, never cleared, thus init with false. + @return 0 if not found, else keyword enumeration. + */ + short GetKeyWord( const OUString& sSymbol, + sal_Int32 nPos, + bool& rbFoundEnglish ) const; + + bool IsAmbiguousE( short nKey ) const // whether nKey is ambiguous E of NF_KEY_E/NF_KEY_EC + { + return (nKey == NF_KEY_EC || nKey == NF_KEY_E) && + (GetKeywords()[NF_KEY_EC] == GetKeywords()[NF_KEY_E]); + } + + // if 0 at strArray[i] is of S,00 or SS,00 or SS"any"00 in ScanType() or FinalScan() + bool Is100SecZero( sal_uInt16 i, bool bHadDecSep ) const; + + short Next_Symbol(const OUString& rStr, + sal_Int32& nPos, + OUString& sSymbol) const; // Next Symbol + sal_Int32 Symbol_Division(const OUString& rString);// Initial lexical scan + sal_Int32 ScanType(); // Analysis of the Format type + sal_Int32 FinalScan( OUString& rString ); // Final analysis with supplied type + + // -1:= error, return nPos in FinalScan; 0:= no calendar, 1:= calendar found + int FinalScanGetCalendar( sal_Int32& nPos, sal_uInt16& i, sal_uInt16& nResultStringsCnt ); + + /** Insert symbol into nTypeArray and sStrArray, e.g. grouping separator. + If at nPos-1 a symbol type NF_SYMBOLTYPE_EMPTY is present, that is + reused instead of shifting all one up and nPos is decremented! */ + bool InsertSymbol( sal_uInt16 & nPos, svt::NfSymbolType eType, const OUString& rStr ); + + /** Whether two key symbols are adjacent separated by date separator. + This can only be used at the end of FinalScan() after + NF_SYMBOLTYPE_DATESEP has already been set. + */ + bool IsDateFragment( size_t nPos1, size_t nPos2 ) const; + + /** Swap nTypeArray and sStrArray elements at positions. */ + void SwapArrayElements( size_t nPos1, size_t nPos2 ); + + static bool StringEqualsChar( const OUString& rStr, sal_Unicode ch ) + { return rStr.getLength() == 1 && rStr[0] == ch; } + + // remove "..." and \... quotes from rStr, return how many chars removed + static sal_Int32 RemoveQuotes( OUString& rStr ); +}; + +#endif // INCLUDED_SVL_SOURCE_NUMBERS_ZFORSCAN_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3