summaryrefslogtreecommitdiffstats
path: root/svl/source/numbers
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /svl/source/numbers
parentInitial commit. (diff)
downloadlibreoffice-cb75148ebd0135178ff46f89a30139c44f8d2040.tar.xz
libreoffice-cb75148ebd0135178ff46f89a30139c44f8d2040.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'svl/source/numbers')
-rw-r--r--svl/source/numbers/currencytable.cxx37
-rw-r--r--svl/source/numbers/numfmuno.cxx988
-rw-r--r--svl/source/numbers/numfmuno.hxx222
-rw-r--r--svl/source/numbers/numuno.cxx89
-rw-r--r--svl/source/numbers/supservs.cxx161
-rw-r--r--svl/source/numbers/supservs.hxx80
-rw-r--r--svl/source/numbers/zforfind.cxx4288
-rw-r--r--svl/source/numbers/zforfind.hxx441
-rw-r--r--svl/source/numbers/zforlist.cxx4972
-rw-r--r--svl/source/numbers/zformat.cxx6035
-rw-r--r--svl/source/numbers/zforscan.cxx3322
-rw-r--r--svl/source/numbers/zforscan.hxx304
12 files changed, 20939 insertions, 0 deletions
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 <svl/currencytable.hxx>
+
+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 <tools/color.hxx>
+#include <o3tl/any.hxx>
+#include <osl/mutex.hxx>
+#include <osl/diagnose.h>
+#include <rtl/ustring.hxx>
+
+#include <com/sun/star/util/Date.hpp>
+#include <com/sun/star/util/MalformedNumberFormatException.hpp>
+#include <com/sun/star/util/NotNumericException.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <comphelper/propertysequence.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+#include "numfmuno.hxx"
+#include <svl/numformat.hxx>
+#include <svl/numuno.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/zformat.hxx>
+#include <svl/itemprop.hxx>
+#include <utility>
+
+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<OUString>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_LOCALE, 0, cppu::UnoType<lang::Locale>::get(),beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_TYPE, 0, cppu::UnoType<sal_Int16>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_COMMENT, 0, cppu::UnoType<OUString>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_CURREXT, 0, cppu::UnoType<OUString>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_CURRSYM, 0, cppu::UnoType<OUString>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_DECIMALS, 0, cppu::UnoType<sal_Int16>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_LEADING, 0, cppu::UnoType<sal_Int16>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_NEGRED, 0, cppu::UnoType<bool>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_STDFORM, 0, cppu::UnoType<bool>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_THOUS, 0, cppu::UnoType<bool>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_USERDEF, 0, cppu::UnoType<bool>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_CURRABB, 0, cppu::UnoType<OUString>::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<bool>::get(), beans::PropertyAttribute::BOUND, 0},
+ {PROPERTYNAME_NULLDATE, 0, cppu::UnoType<util::Date>::get(), beans::PropertyAttribute::BOUND, 0},
+ {PROPERTYNAME_STDDEC, 0, cppu::UnoType<sal_Int16>::get(), beans::PropertyAttribute::BOUND, 0},
+ {PROPERTYNAME_TWODIGIT, 0, cppu::UnoType<sal_Int16>::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<util::XNumberFormatsSupplier>& _xSupplier )
+{
+ ::rtl::Reference< SvNumberFormatsSupplierObj > xAutoReleaseOld;
+
+ // SYNCHRONIZED ->
+ {
+ ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() );
+
+ SvNumberFormatsSupplierObj* pNew = comphelper::getFromUnoTunnel<SvNumberFormatsSupplierObj>( _xSupplier );
+ if (!pNew)
+ throw uno::RuntimeException(); // wrong object
+
+ xAutoReleaseOld = xSupplier;
+
+ xSupplier = pNew;
+ m_aMutex = xSupplier->getSharedMutex();
+ }
+ // <- SYNCHRONIZED
+}
+
+uno::Reference<util::XNumberFormatsSupplier> 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<OUString> 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<beans::XPropertySet> 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_Int32> 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<SvNumFormatType>(nType), nIndex, eLang ) :
+ pFormatter->GetEntryTable( static_cast<SvNumFormatType>(nType), nIndex, eLang );
+ sal_uInt32 nCount = rTable.size();
+ uno::Sequence<sal_Int32> 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<sal_Int32>(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<SvNumFormatType>(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<NfIndexTableOffset>(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<SvNumFormatType>(nOldType), static_cast<SvNumFormatType>(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<OUString> 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<beans::XPropertySetInfo> SAL_CALL SvNumberFormatObj::getPropertySetInfo()
+{
+ static uno::Reference<beans::XPropertySetInfo> 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<sal_Int16>( 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<sal_Int16>(nDecimals);
+ }
+ else if (aPropertyName == PROPERTYNAME_LEADING)
+ {
+ pFormat->GetFormatSpecialInfo( bThousand, bRed, nDecimals, nLeading );
+ aRet <<= static_cast<sal_Int16>(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<beans::XPropertyChangeListener>&)
+{
+ OSL_FAIL("not implemented");
+}
+
+void SAL_CALL SvNumberFormatObj::removePropertyChangeListener( const OUString&,
+ const uno::Reference<beans::XPropertyChangeListener>&)
+{
+ OSL_FAIL("not implemented");
+}
+
+void SAL_CALL SvNumberFormatObj::addVetoableChangeListener( const OUString&,
+ const uno::Reference<beans::XVetoableChangeListener>&)
+{
+ OSL_FAIL("not implemented");
+}
+
+void SAL_CALL SvNumberFormatObj::removeVetoableChangeListener( const OUString&,
+ const uno::Reference<beans::XVetoableChangeListener>&)
+{
+ OSL_FAIL("not implemented");
+}
+
+// XPropertyAccess
+
+uno::Sequence<beans::PropertyValue> 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<beans::PropertyValue> 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<beans::PropertyValue>& )
+{
+ 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<OUString> 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<beans::XPropertySetInfo> SAL_CALL SvNumberFormatSettingsObj::getPropertySetInfo()
+{
+ static uno::Reference<beans::XPropertySetInfo> 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<bool>(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<sal_Int16>( pFormatter->GetStandardPrec() );
+ else if (aPropertyName == PROPERTYNAME_TWODIGIT)
+ aRet <<= static_cast<sal_Int16>( pFormatter->GetYear2000() );
+ else
+ throw beans::UnknownPropertyException(aPropertyName);
+
+ return aRet;
+}
+
+void SAL_CALL SvNumberFormatSettingsObj::addPropertyChangeListener( const OUString&,
+ const uno::Reference<beans::XPropertyChangeListener>&)
+{
+ OSL_FAIL("not implemented");
+}
+
+void SAL_CALL SvNumberFormatSettingsObj::removePropertyChangeListener( const OUString&,
+ const uno::Reference<beans::XPropertyChangeListener>&)
+{
+ OSL_FAIL("not implemented");
+}
+
+void SAL_CALL SvNumberFormatSettingsObj::addVetoableChangeListener( const OUString&,
+ const uno::Reference<beans::XVetoableChangeListener>&)
+{
+ OSL_FAIL("not implemented");
+}
+
+void SAL_CALL SvNumberFormatSettingsObj::removeVetoableChangeListener( const OUString&,
+ const uno::Reference<beans::XVetoableChangeListener>&)
+{
+ 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<OUString> 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<css::uno::Any> 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 <com/sun/star/util/XNumberFormatter2.hpp>
+#include <com/sun/star/util/XNumberFormats.hpp>
+#include <com/sun/star/util/XNumberFormatTypes.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/beans/XPropertyAccess.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/sharedmutex.hxx>
+#include <rtl/ref.hxx>
+#include <tools/solar.h>
+
+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<SvNumberFormatsSupplierObj> 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<SvNumberFormatsSupplierObj>
+ 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<SvNumberFormatsSupplierObj>
+ 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 <svl/numuno.hxx>
+#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<beans::XPropertySet> SAL_CALL SvNumberFormatsSupplierObj::getNumberFormatSettings()
+{
+ ::osl::MutexGuard aGuard( pImpl->aMutex );
+
+ return new SvNumberFormatSettingsObj( *this, pImpl->aMutex );
+}
+
+uno::Reference<util::XNumberFormats> 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 <com/sun/star/lang/Locale.hpp>
+#include <comphelper/sharedmutex.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <cppuhelper/queryinterface.hxx>
+#include <cppuhelper/weak.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <svl/numformat.hxx>
+#include <tools/debug.hxx>
+#include <osl/mutex.hxx>
+#include <utility>
+#include <osl/diagnose.h>
+
+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<css::lang::Locale>::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<css::uno::Any> 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 <svl/numuno.hxx>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <memory>
+
+/**
+ * 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<SvNumberFormatter> 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 <cstdlib>
+#include <dtoa.h>
+#include <float.h>
+#include <comphelper/string.hxx>
+#include <sal/log.hxx>
+#include <tools/date.hxx>
+#include <rtl/math.hxx>
+#include <rtl/character.hxx>
+#include <unotools/charclass.hxx>
+#include <unotools/calendarwrapper.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <com/sun/star/i18n/CalendarFieldIndex.hpp>
+#include <com/sun/star/i18n/LocaleCalendar2.hpp>
+#include <unotools/digitgroupingiterator.hxx>
+#include <comphelper/sequence.hxx>
+
+#include <svl/zforlist.hxx>
+#include "zforscan.hxx"
+#include <svl/zformat.hxx>
+
+#include <memory>
+
+#include "zforfind.hxx"
+
+#ifndef DBG_UTIL
+#define NF_TEST_CALENDAR 0
+#else
+#define NF_TEST_CALENDAR 0
+#endif
+#if NF_TEST_CALENDAR
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/i18n/XCalendar4.hpp>
+#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<char[]> 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<char[]>(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<char>(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<sal_uInt16>(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<sal_uInt16>(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<sal_uInt16>(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<double>(nHour)*3600 +
+ static_cast<double>(nMinute)*60 +
+ static_cast<double>(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<sal_uInt16>(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<sal_uInt16>(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<sal_uInt16>(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<OUString> 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<sal_Int32>(nZO) * 60000 +
+ (nZO < 0 ? -1 : 1) * static_cast<sal_uInt16>(nZOmillis);
+ nDST1 = xCal->getValue( i18n::CalendarFieldIndex::DST_OFFSET );
+ nDST1millis = xCal->getValue( i18n::CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS );
+ nDST1InMillis = static_cast<sal_Int32>(nDST1) * 60000 +
+ (nDST1 < 0 ? -1 : 1) * static_cast<sal_uInt16>(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<sal_Int32>(nDST2) * 60000 +
+ (nDST2 < 0 ? -1 : 1) * static_cast<sal_uInt16>(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 <com/sun/star/uno/Sequence.hxx>
+#include <rtl/ustring.hxx>
+#include <svl/zforlist.hxx>
+#include <memory>
+
+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<OUString[]> pUpperMonthText; //* Array of month names, uppercase
+ std::unique_ptr<OUString[]> pUpperAbbrevMonthText; //* Array of month names, abbreviated, uppercase
+ std::unique_ptr<OUString[]> pUpperGenitiveMonthText; //* Array of genitive month names, uppercase
+ std::unique_ptr<OUString[]> pUpperGenitiveAbbrevMonthText; //* Array of genitive month names, abbreviated, uppercase
+ std::unique_ptr<OUString[]> pUpperPartitiveMonthText; //* Array of partitive month names, uppercase
+ std::unique_ptr<OUString[]> pUpperPartitiveAbbrevMonthText;//* Array of partitive month names, abbreviated, uppercase
+ std::unique_ptr<OUString[]> pUpperDayText; //* Array of day of week names, uppercase
+ std::unique_ptr<OUString[]> 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<Date> 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 <TRUE/> 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 <sal/config.h>
+
+#include <sal/log.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/currencytable.hxx>
+
+#include <comphelper/string.hxx>
+#include <tools/debug.hxx>
+#include <unotools/charclass.hxx>
+#include <unotools/configmgr.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <com/sun/star/i18n/KNumberFormatUsage.hpp>
+#include <com/sun/star/i18n/KNumberFormatType.hpp>
+#include <com/sun/star/i18n/FormatElement.hpp>
+#include <com/sun/star/i18n/Currency2.hpp>
+#include <com/sun/star/i18n/NumberFormatCode.hpp>
+#include <com/sun/star/i18n/XNumberFormatCode.hpp>
+#include <com/sun/star/i18n/NumberFormatMapper.hpp>
+#include <comphelper/processfactory.hxx>
+
+#include <osl/mutex.hxx>
+
+#include "zforscan.hxx"
+#include "zforfind.hxx"
+#include <svl/zformat.hxx>
+#include <i18npool/reservedconstants.hxx>
+
+#include <unotools/syslocaleoptions.hxx>
+#include <unotools/digitgroupingiterator.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/math.hxx>
+
+#include <math.h>
+#include <limits>
+#include <memory>
+#include <set>
+
+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<sal_uInt16>::max();
+const sal_uInt16 SvNumberFormatter::INPUTSTRING_PRECISION = ::std::numeric_limits<sal_uInt16>::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<sal_uInt16,Color*>& 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<SvNumberformat> 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<SvNumberformat> 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<css::uno::XComponentContext>& 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<SvNumberformat> 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<sal_uInt16>(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<LanguageType>& 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<SvNumberformat> 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<SvNumberformat> 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<SvNumberFormatter*>(this)->xLocaleData.changeLocale( LanguageTag( nLang));
+ aRet = xLocaleData->getNumDecimalSep();
+ const_cast<SvNumberFormatter*>(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<sal_Int32>(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<sal_Int32>(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<sal_Int32>(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<sal_Int32>(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<sal_Int32>(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<SvNumberFormatter*>(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<SvNumberformat> 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<sal_uInt16>(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<nLength && 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<SvNumberformat> 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<SvNumberformat> 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<sal_uInt16>(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<NfIndexTableOffset>(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<LocaleDataWrapper> 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<NfCurrencyEntry> 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; j<n; j++ )
+ {
+ if ( rCurrencyTable[aCurrencyIndex++] == *pEntry )
+ {
+ bInsert = false;
+ break; // for
+ }
+ }
+ if ( !bInsert )
+ {
+ pEntry.reset();
+ }
+ else
+ {
+ if ( !nSecondarySystemCurrencyPosition &&
+ (!aConfiguredCurrencyAbbrev.isEmpty() ?
+ pEntry->GetBankSymbol() == 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<sal_uInt16>(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<Color> & 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 <stdio.h>
+#include <string_view>
+
+#include <o3tl/string_view.hxx>
+#include <comphelper/string.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+#include <tools/long.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <rtl/math.hxx>
+#include <unotools/charclass.hxx>
+#include <unotools/calendarwrapper.hxx>
+#include <unotools/nativenumberwrapper.hxx>
+#include <com/sun/star/i18n/CalendarFieldIndex.hpp>
+#include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
+#include <com/sun/star/i18n/CalendarDisplayCode.hpp>
+#include <com/sun/star/i18n/AmPmValue.hpp>
+#include <com/sun/star/i18n/NativeNumberMode.hpp>
+#include <com/sun/star/i18n/NativeNumberXmlAttributes2.hpp>
+
+#include <svl/zformat.hxx>
+#include "zforscan.hxx"
+
+#include "zforfind.hxx"
+#include <svl/zforlist.hxx>
+#include <unotools/digitgroupingiterator.hxx>
+#include <svl/nfsymbol.hxx>
+
+#include <cmath>
+#include <array>
+
+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<int>(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<tools::Long>(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<LanguageType, std::array<sal_uInt8, 4>> 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<LanguageType, std::array<sal_uInt8, 9>> 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<nStringsCnt; j++ )
+ {
+ if ( aI.nTypeArray[j] == NF_SYMBOLTYPE_CURRENCY )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ImpSvNumFor::GetNewCurrencySymbol( OUString& rSymbol,
+ OUString& rExtension ) const
+{
+ for ( sal_uInt16 j=0; j<nStringsCnt; j++ )
+ {
+ if ( aI.nTypeArray[j] == NF_SYMBOLTYPE_CURRENCY )
+ {
+ rSymbol = aI.sStrArray[j];
+ if ( j < nStringsCnt-1 && aI.nTypeArray[j+1] == NF_SYMBOLTYPE_CURREXT )
+ {
+ rExtension = aI.sStrArray[j+1];
+ }
+ else
+ {
+ rExtension.clear();
+ }
+ return true;
+ }
+ }
+ //! No Erase at rSymbol, rExtension
+ return false;
+}
+
+/**
+ * SvNumberformat
+ */
+
+namespace {
+
+enum BracketFormatSymbolType
+{
+ BRACKET_SYMBOLTYPE_FORMAT = -1, // subformat string
+ BRACKET_SYMBOLTYPE_COLOR = -2, // color
+ BRACKET_SYMBOLTYPE_ERROR = -3, // error
+ BRACKET_SYMBOLTYPE_DBNUM1 = -4, // DoubleByteNumber, represent numbers
+ BRACKET_SYMBOLTYPE_DBNUM2 = -5, // using CJK characters, Excel compatible
+ BRACKET_SYMBOLTYPE_DBNUM3 = -6,
+ BRACKET_SYMBOLTYPE_DBNUM4 = -7,
+ BRACKET_SYMBOLTYPE_DBNUM5 = -8,
+ BRACKET_SYMBOLTYPE_DBNUM6 = -9,
+ BRACKET_SYMBOLTYPE_DBNUM7 = -10,
+ BRACKET_SYMBOLTYPE_DBNUM8 = -11,
+ BRACKET_SYMBOLTYPE_DBNUM9 = -12,
+ BRACKET_SYMBOLTYPE_LOCALE = -13,
+ BRACKET_SYMBOLTYPE_NATNUM0 = -14, // Our NativeNumber support, ASCII
+ BRACKET_SYMBOLTYPE_NATNUM1 = -15, // Our NativeNumber support, represent
+ BRACKET_SYMBOLTYPE_NATNUM2 = -16, // numbers using CJK, CTL, ...
+ BRACKET_SYMBOLTYPE_NATNUM3 = -17,
+ BRACKET_SYMBOLTYPE_NATNUM4 = -18,
+ BRACKET_SYMBOLTYPE_NATNUM5 = -19,
+ BRACKET_SYMBOLTYPE_NATNUM6 = -20,
+ BRACKET_SYMBOLTYPE_NATNUM7 = -21,
+ BRACKET_SYMBOLTYPE_NATNUM8 = -22,
+ BRACKET_SYMBOLTYPE_NATNUM9 = -23,
+ BRACKET_SYMBOLTYPE_NATNUM10 = -24,
+ BRACKET_SYMBOLTYPE_NATNUM11 = -25,
+ BRACKET_SYMBOLTYPE_NATNUM12 = -26,
+ BRACKET_SYMBOLTYPE_NATNUM13 = -27,
+ BRACKET_SYMBOLTYPE_NATNUM14 = -28,
+ BRACKET_SYMBOLTYPE_NATNUM15 = -29,
+ BRACKET_SYMBOLTYPE_NATNUM16 = -30,
+ BRACKET_SYMBOLTYPE_NATNUM17 = -31,
+ BRACKET_SYMBOLTYPE_NATNUM18 = -32,
+ BRACKET_SYMBOLTYPE_NATNUM19 = -33
+};
+
+}
+
+void SvNumberformat::ImpCopyNumberformat( const SvNumberformat& rFormat )
+{
+ sFormatstring = rFormat.sFormatstring;
+ eType = rFormat.eType;
+ maLocale = rFormat.maLocale;
+ fLimit1 = rFormat.fLimit1;
+ fLimit2 = rFormat.fLimit2;
+ eOp1 = rFormat.eOp1;
+ eOp2 = rFormat.eOp2;
+ bStandard = rFormat.bStandard;
+ bIsUsed = rFormat.bIsUsed;
+ sComment = rFormat.sComment;
+ bAdditionalBuiltin = rFormat.bAdditionalBuiltin;
+
+ // #121103# when copying between documents, get color pointers from own scanner
+ ImpSvNumberformatScan* pColorSc = ( &rScan != &rFormat.rScan ) ? &rScan : nullptr;
+
+ for (sal_uInt16 i = 0; i < 4; i++)
+ {
+ NumFor[i].Copy(rFormat.NumFor[i], pColorSc);
+ }
+}
+
+SvNumberformat::SvNumberformat( SvNumberformat const & rFormat )
+ : rScan(rFormat.rScan), bStarFlag( rFormat.bStarFlag )
+{
+ ImpCopyNumberformat( rFormat );
+}
+
+SvNumberformat::SvNumberformat( SvNumberformat const & rFormat, ImpSvNumberformatScan& rSc )
+ : rScan(rSc)
+ , bStarFlag( rFormat.bStarFlag )
+{
+ ImpCopyNumberformat( rFormat );
+}
+
+static bool lcl_SvNumberformat_IsBracketedPrefix( short nSymbolType )
+{
+ if ( nSymbolType > 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<SvNumberformatLimitOps>(eSymbolType);
+ }
+ else if ( nIndex == 1 && bCondition )
+ {
+ eOp2 = static_cast<SvNumberformatLimitOps>(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<sal_uInt8>(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<sal_uInt8>(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<sal_Int32>( sBuff.indexOf(']') + 1, 1);
+ }
+ else if (IsSystemLongDateFormat())
+ {
+ if ((eType & ~SvNumFormatType::DEFINED) != SvNumFormatType::DATE)
+ nCheckPos = std::max<sal_Int32>( 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<sal_uInt16>(
+ (meLanguageWithoutLocaleData == LANGUAGE_DONTKNOW) ? meLanguage :
+ meLanguageWithoutLocaleData);
+ if (meLanguage == LANGUAGE_SYSTEM)
+ {
+ switch (meSubstitute)
+ {
+ case Substitute::NONE:
+ ; // nothing
+ break;
+ case Substitute::TIME:
+ n16 = static_cast<sal_uInt16>(LANGUAGE_NF_SYSTEM_TIME);
+ break;
+ case Substitute::LONGDATE:
+ n16 = static_cast<sal_uInt16>(LANGUAGE_NF_SYSTEM_DATE);
+ break;
+ }
+ }
+ for (sal_uInt8 i = 0; i < 4; ++i)
+ {
+ sal_uInt8 n = static_cast<sal_uInt8>((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<LanguageType>(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<sal_uInt8>(nRawNum & 0xFF);
+ nRawNum = (nRawNum >> 8);
+ mnNumeralShape = static_cast<sal_uInt8>(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<short>(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<sal_uInt16>(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<typename T>
+bool checkForAll0s(const T& rString, sal_Int32 nIdx=0)
+{
+ if (nIdx>=rString.getLength())
+ return false;
+
+ do
+ {
+ if (rString[nIdx]!='0')
+ return false;
+ }
+ while (++nIdx<rString.getLength());
+
+ return true;
+}
+
+}
+
+void SvNumberformat::ImpGetOutputStdToPrecision(double& rNumber, OUString& rOutString, sal_uInt16 nPrecision) const
+{
+ // Make sure the precision doesn't go over the maximum allowable precision.
+ nPrecision = ::std::min(UPPER_PRECISION, nPrecision);
+
+ // We decided to strip trailing zeros unconditionally, since binary
+ // double-precision rounding error makes it impossible to determine e.g.
+ // whether 844.10000000000002273737 is what the user has typed, or the
+ // user has typed 844.1 but IEEE 754 represents it that way internally.
+
+ rOutString = ::rtl::math::doubleToUString( rNumber,
+ rtl_math_StringFormat_F, nPrecision /*2*/,
+ GetFormatter().GetNumDecimalSep()[0], true );
+ if (rOutString[0] == '-' && checkForAll0s(rOutString, 1))
+ {
+ rOutString = comphelper::string::stripStart(rOutString, '-'); // not -0
+ }
+ rOutString = impTransliterate(rOutString, NumFor[0].GetNatNum());
+}
+
+void SvNumberformat::ImpGetOutputInputLine(double fNumber, OUString& OutString) const
+{
+ bool bModified = false;
+ if ( (eType & SvNumFormatType::PERCENT) && (fabs(fNumber) < D_MAX_D_BY_100))
+ {
+ if (fNumber == 0.0)
+ {
+ OutString = "0%";
+ return;
+ }
+ fNumber *= 100;
+ bModified = true;
+ }
+
+ if (fNumber == 0.0)
+ {
+ OutString = "0";
+ return;
+ }
+
+ OutString = ::rtl::math::doubleToUString( fNumber,
+ rtl_math_StringFormat_Automatic,
+ rtl_math_DecimalPlaces_Max,
+ GetFormatter().GetNumDecimalSep()[0], true );
+
+ if ( eType & SvNumFormatType::PERCENT && bModified)
+ {
+ OutString += "%";
+ }
+}
+
+short SvNumberformat::ImpCheckCondition(double fNumber,
+ double fLimit,
+ SvNumberformatLimitOps eOp)
+{
+ switch(eOp)
+ {
+ case NUMBERFORMAT_OP_NO:
+ return -1;
+ case NUMBERFORMAT_OP_EQ:
+ return static_cast<short>(fNumber == fLimit);
+ case NUMBERFORMAT_OP_NE:
+ return static_cast<short>(fNumber != fLimit);
+ case NUMBERFORMAT_OP_LT:
+ return static_cast<short>(fNumber < fLimit);
+ case NUMBERFORMAT_OP_LE:
+ return static_cast<short>(fNumber <= fLimit);
+ case NUMBERFORMAT_OP_GT:
+ return static_cast<short>(fNumber > fLimit);
+ case NUMBERFORMAT_OP_GE:
+ return static_cast<short>(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<sal_uInt16>(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<sal_uInt16>(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<sal_Int32>(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<sal_Int32>(rInfo.nCntPre) : -1;
+ if( nRescale < 0 && rInfo.nCntPre != 0 )
+ nRescale += static_cast<sal_Int32>(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<double>(nFrac) / static_cast<double>(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<sal_Int64>(floor ( fNumber * nDiv ));
+ double fFracNew = static_cast<double>(nFrac) / static_cast<double>(nDiv);
+ double fFracNew1 = static_cast<double>(nFrac + 1) / static_cast<double>(nDiv);
+ double fDiff = fNumber - fFracNew;
+ if( fDiff > ( fFracNew1 - fNumber ) )
+ {
+ nFrac++;
+ }
+ }
+ else // Calculated Denominator
+ {
+ nDiv = 1;
+ sal_Int64 nBasis = static_cast<sal_Int64>(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<sal_Int64>(floor(fTemp)); // due to floating point notation with double precision
+ fRemainder = fTemp - static_cast<double>(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<double>(nFrac) > fNumber * static_cast<double>(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<double>(nDiv) + static_cast<double>(nFrac);
+
+ if (fNum1 > D_MAX_INTEGER)
+ {
+ sBuff = ImpSvNumberformatScan::sErrStr;
+ return false;
+ }
+ nFrac = static_cast<sal_Int64>(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<sal_uInt16>(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<sal_uInt32>(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<NfKeywordIndex>(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<sal_uInt32>(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<NfKeywordIndex>(rInfo.nTypeArray[i])),
+ nNatNum));
+ break;
+ case NF_KEY_MMMM: // MMMM
+ sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx],
+ static_cast<NfKeywordIndex>(rInfo.nTypeArray[i])),
+ nNatNum));
+ break;
+ case NF_KEY_MMMMM: // MMMMM
+ sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx],
+ static_cast<NfKeywordIndex>(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<tools::Long>(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<nCnt; j++ )
+ {
+ switch ( rTypeArray[j] )
+ {
+ case NF_KEY_D :
+ case NF_KEY_DD :
+ return DateOrder::DMY;
+ case NF_KEY_M :
+ case NF_KEY_MM :
+ case NF_KEY_MMM :
+ case NF_KEY_MMMM :
+ case NF_KEY_MMMMM :
+ return DateOrder::MDY;
+ case NF_KEY_YY :
+ case NF_KEY_YYYY :
+ case NF_KEY_EC :
+ case NF_KEY_EEC :
+ case NF_KEY_R :
+ case NF_KEY_RR :
+ return DateOrder::YMD;
+ }
+ }
+ }
+ else
+ {
+ SAL_WARN( "svl.numbers", "SvNumberformat::GetDateOrder: no date" );
+ }
+ return rLoc().getDateOrder();
+}
+
+sal_uInt32 SvNumberformat::GetExactDateOrder() const
+{
+ sal_uInt32 nRet = 0;
+ if ( !(eType & SvNumFormatType::DATE) )
+ {
+ SAL_WARN( "svl.numbers", "SvNumberformat::GetExactDateOrder: no date" );
+ return nRet;
+ }
+ auto& rTypeArray = NumFor[0].Info().nTypeArray;
+ sal_uInt16 nCnt = NumFor[0].GetCount();
+ int nShift = 0;
+ for ( sal_uInt16 j=0; j<nCnt && nShift < 3; j++ )
+ {
+ switch ( rTypeArray[j] )
+ {
+ case NF_KEY_D :
+ case NF_KEY_DD :
+ nRet = (nRet << 8) | 'D';
+ ++nShift;
+ break;
+ case NF_KEY_M :
+ case NF_KEY_MM :
+ case NF_KEY_MMM :
+ case NF_KEY_MMMM :
+ case NF_KEY_MMMMM :
+ nRet = (nRet << 8) | 'M';
+ ++nShift;
+ break;
+ case NF_KEY_YY :
+ case NF_KEY_YYYY :
+ case NF_KEY_EC :
+ case NF_KEY_EEC :
+ case NF_KEY_R :
+ case NF_KEY_RR :
+ nRet = (nRet << 8) | 'Y';
+ ++nShift;
+ break;
+ }
+ }
+ return nRet;
+}
+
+void SvNumberformat::GetConditions( SvNumberformatLimitOps& rOper1, double& rVal1,
+ SvNumberformatLimitOps& rOper2, double& rVal2 ) const
+{
+ rOper1 = eOp1;
+ rOper2 = eOp2;
+ rVal1 = fLimit1;
+ rVal2 = fLimit2;
+}
+
+const Color* SvNumberformat::GetColor( sal_uInt16 nNumFor ) const
+{
+ if ( nNumFor > 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<nCnt; j++ )
+ {
+ if ( 0 <= rTypeArray[j] && rTypeArray[j] < NF_KEYWORD_ENTRIES_COUNT )
+ {
+ aStr.append( rKeywords[rTypeArray[j]] );
+ if( NF_KEY_NNNN == rTypeArray[j] )
+ {
+ aStr.append( rLocWrp.getLongDateDayOfWeekSep() );
+ }
+ switch (rTypeArray[j])
+ {
+ case NF_KEY_EC:
+ case NF_KEY_EEC:
+ case NF_KEY_R:
+ case NF_KEY_RR:
+ // Implicit secondary (non-gregorian) calendar.
+ // Currently only for ja-JP.
+ /* TODO: same for all locales in
+ * LocaleDataWrapper::doesSecondaryCalendarUseEC() ?
+ * Should split the locales off that then. */
+ if (!nCalendarID)
+ {
+ const LanguageType nLang = MsLangId::getRealLanguage( nOriginalLang);
+ if (nLang == LANGUAGE_JAPANESE)
+ nCalendarID = kCalGengou;
+ }
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ else
+ {
+ switch ( rTypeArray[j] )
+ {
+ case NF_SYMBOLTYPE_DECSEP :
+ aStr.append( rLocWrp.getNumDecimalSep() );
+ break;
+ case NF_SYMBOLTYPE_THSEP :
+ aStr.append( rLocWrp.getNumThousandSep() );
+ break;
+ case NF_SYMBOLTYPE_EXP :
+ aStr.append( rKeywords[NF_KEY_E] );
+ if ( rStrArray[j].getLength() > 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<sal_uInt16>(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<nNumForCnt; ++j )
+ {
+ switch ( rTypeArray[j] )
+ {
+ case NF_SYMBOLTYPE_STRING:
+ case NF_SYMBOLTYPE_CURRENCY:
+ case NF_SYMBOLTYPE_DATESEP:
+ case NF_SYMBOLTYPE_TIMESEP:
+ case NF_SYMBOLTYPE_TIME100SECSEP:
+ case NF_SYMBOLTYPE_PERCENT:
+ ++nCnt;
+ break;
+ }
+ }
+ return nCnt;
+}
+
+bool SvNumberformat::IsMinuteSecondFormat() const
+{
+ if (GetMaskedType() != SvNumFormatType::TIME)
+ return false;
+
+ constexpr sal_uInt16 k00 = 0x00; // Nada, Nilch
+ constexpr sal_uInt16 kLB = 0x01; // '[' Left Bracket
+ constexpr sal_uInt16 kRB = 0x02; // ']' Right Bracket
+ constexpr sal_uInt16 kMM = 0x04; // M or MM
+ constexpr sal_uInt16 kTS = 0x08; // Time Separator
+ constexpr sal_uInt16 kSS = 0x10; // S or SS
+#define HAS_MINUTE_SECOND(state) ((state) == (kMM|kTS|kSS) || (state) == (kLB|kMM|kRB|kTS|kSS))
+ // Also (kMM|kTS|kLB|kSS|kRB) but those are the same bits.
+
+ sal_uInt16 nState = k00;
+ bool bSep = false;
+ sal_uInt16 nNumForCnt = NumFor[0].GetCount();
+ auto const & rTypeArray = NumFor[0].Info().nTypeArray;
+ for (sal_uInt16 j=0; j < nNumForCnt; ++j)
+ {
+ switch (rTypeArray[j])
+ {
+ case NF_SYMBOLTYPE_DEL:
+ {
+ // '[' or ']' before/after MM or SS
+ const OUString& rStr = NumFor[0].Info().sStrArray[j];
+ if (rStr == "[")
+ {
+ if (nState != k00 && nState != (kMM|kTS))
+ return false;
+ nState |= kLB;
+ }
+ else if (rStr == "]")
+ {
+ if (nState != (kLB|kMM) && nState != (kMM|kTS|kLB|kSS))
+ return false;
+ nState |= kRB;
+ }
+ else
+ return false;
+ }
+ break;
+ case NF_KEY_MI:
+ case NF_KEY_MMI:
+ if (nState != k00 && nState != kLB)
+ return false;
+ nState |= kMM;
+ break;
+ case NF_SYMBOLTYPE_TIMESEP:
+ if (nState != kMM && nState != (kLB|kMM|kRB))
+ return false;
+ nState |= kTS;
+ break;
+ case NF_KEY_S:
+ case NF_KEY_SS:
+ if (nState != (kMM|kTS) && nState != (kLB|kMM|kRB|kTS) && nState != (kMM|kTS|kLB))
+ return false;
+ nState |= kSS;
+ break;
+ case NF_SYMBOLTYPE_TIME100SECSEP:
+ // Trailing fraction of seconds allowed.
+ if (!HAS_MINUTE_SECOND(nState))
+ return false;
+ bSep = true;
+ break;
+ case NF_SYMBOLTYPE_DIGIT:
+ if (!bSep)
+ return false;
+ break;
+ case NF_SYMBOLTYPE_STRING:
+ // nothing, display literal
+ break;
+ default:
+ return false;
+ }
+ }
+ return HAS_MINUTE_SECOND(nState);
+#undef HAS_MINUTE_SECOND
+}
+
+OUString SvNumberformat::GetFormatStringForTimePrecision( int nPrecision ) const
+{
+ OUStringBuffer sString;
+ using comphelper::string::padToLength;
+
+ sal_uInt16 nNumForCnt = NumFor[0].GetCount();
+ auto const & rTypeArray = NumFor[0].Info().nTypeArray;
+ for (sal_uInt16 j=0; j < nNumForCnt; ++j)
+ {
+ switch (rTypeArray[j])
+ {
+ case NF_KEY_S :
+ case NF_KEY_SS:
+ sString.append( NumFor[0].Info().sStrArray[j] );
+ if ( 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 <stdlib.h>
+#include <comphelper/string.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <unotools/charclass.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <com/sun/star/i18n/NumberFormatCode.hpp>
+#include <com/sun/star/i18n/NumberFormatMapper.hpp>
+
+#include <svl/zforlist.hxx>
+#include <svl/zformat.hxx>
+#include <unotools/digitgroupingiterator.hxx>
+
+#include "zforscan.hxx"
+
+#include <svl/nfsymbol.hxx>
+using namespace svt;
+
+const sal_Unicode cNoBreakSpace = 0xA0;
+const sal_Unicode cNarrowNoBreakSpace = 0x202F;
+
+const int MaxCntPost = 20; //max dec places allow by rtl_math_round
+
+const NfKeywordTable ImpSvNumberformatScan::sEnglishKeyword =
+{ // Syntax keywords in English (USA)
+ //! All keywords MUST be UPPERCASE! In same order as NfKeywordIndex
+ "", // NF_KEY_NONE 0
+ "E", // NF_KEY_E Exponent
+ "AM/PM", // NF_KEY_AMPM AM/PM
+ "A/P", // NF_KEY_AP AM/PM short
+ "M", // NF_KEY_MI Minute
+ "MM", // NF_KEY_MMI Minute 02
+ "M", // NF_KEY_M month (!)
+ "MM", // NF_KEY_MM month 02 (!)
+ "MMM", // NF_KEY_MMM month short name
+ "MMMM", // NF_KEY_MMMM month long name
+ "MMMMM", // NF_KEY_MMMMM first letter of month name
+ "H", // NF_KEY_H hour
+ "HH", // NF_KEY_HH hour 02
+ "S", // NF_KEY_S Second
+ "SS", // NF_KEY_SS Second 02
+ "Q", // NF_KEY_Q Quarter short 'Q'
+ "QQ", // NF_KEY_QQ Quarter long
+ "D", // NF_KEY_D day of month
+ "DD", // NF_KEY_DD day of month 02
+ "DDD", // NF_KEY_DDD day of week short
+ "DDDD", // NF_KEY_DDDD day of week long
+ "YY", // NF_KEY_YY year two digits
+ "YYYY", // NF_KEY_YYYY year four digits
+ "NN", // NF_KEY_NN Day of week short
+ "NNN", // NF_KEY_NNN Day of week long
+ "NNNN", // NF_KEY_NNNN Day of week long incl. separator
+ "AAA", // NF_KEY_AAA
+ "AAAA", // NF_KEY_AAAA
+ "E", // NF_KEY_EC
+ "EE", // NF_KEY_EEC
+ "G", // NF_KEY_G
+ "GG", // NF_KEY_GG
+ "GGG", // NF_KEY_GGG
+ "R", // NF_KEY_R
+ "RR", // NF_KEY_RR
+ "WW", // NF_KEY_WW Week of year
+ "t", // NF_KEY_THAI_T Thai T modifier, speciality of Thai Excel, only
+ // used with Thai locale and converted to [NatNum1], only
+ // exception as lowercase
+ "CCC", // NF_KEY_CCC Currency abbreviation
+ "BOOLEAN", // NF_KEY_BOOLEAN boolean
+ "GENERAL", // NF_KEY_GENERAL General / Standard
+
+ // Reserved words translated and color names follow:
+ "TRUE", // NF_KEY_TRUE boolean true
+ "FALSE", // NF_KEY_FALSE boolean false
+ "COLOR", // NF_KEY_COLOR color
+ // colours
+ "BLACK", // NF_KEY_BLACK
+ "BLUE", // NF_KEY_BLUE
+ "GREEN", // NF_KEY_GREEN
+ "CYAN", // NF_KEY_CYAN
+ "RED", // NF_KEY_RED
+ "MAGENTA", // NF_KEY_MAGENTA
+ "BROWN", // NF_KEY_BROWN
+ "GREY", // NF_KEY_GREY
+ "YELLOW", // NF_KEY_YELLOW
+ "WHITE" // NF_KEY_WHITE
+};
+
+const ::std::vector<Color> ImpSvNumberformatScan::StandardColor{
+ COL_BLACK, COL_LIGHTBLUE, COL_LIGHTGREEN, COL_LIGHTCYAN, COL_LIGHTRED,
+ COL_LIGHTMAGENTA, COL_BROWN, COL_GRAY, COL_YELLOW, COL_WHITE
+};
+
+// This vector will hold *only* the color names in German language.
+static const std::u16string_view& GermanColorName(size_t i)
+{
+ static const std::u16string_view sGermanColorNames[]{ u"FARBE", u"SCHWARZ", u"BLAU",
+ u"GRÃœN", u"CYAN", u"ROT",
+ u"MAGENTA", u"BRAUN", u"GRAU",
+ u"GELB", u"WEISS" };
+ assert(i < SAL_N_ELEMENTS(sGermanColorNames));
+ return sGermanColorNames[i];
+}
+
+ImpSvNumberformatScan::ImpSvNumberformatScan( SvNumberFormatter* pFormatterP )
+ : maNullDate( 30, 12, 1899)
+ , eNewLnge(LANGUAGE_DONTKNOW)
+ , eTmpLnge(LANGUAGE_DONTKNOW)
+ , nCurrPos(-1)
+ , meKeywordLocalization(KeywordLocalization::AllowEnglish)
+{
+ pFormatter = pFormatterP;
+ xNFC = css::i18n::NumberFormatMapper::create( pFormatter->GetComponentContext() );
+ bConvertMode = false;
+ mbConvertDateOrder = false;
+ bConvertSystemToSystem = false;
+ bKeywordsNeedInit = true; // locale dependent and not locale dependent keywords
+ bCompatCurNeedInit = true; // locale dependent compatibility currency strings
+
+ static_assert( NF_KEY_BLACK - NF_KEY_COLOR == 1, "bad FARBE(COLOR), SCHWARZ(BLACK) sequence");
+ static_assert( NF_KEY_FIRSTCOLOR - NF_KEY_COLOR == 1, "bad color sequence");
+ static_assert( NF_MAX_DEFAULT_COLORS + 1 == 11, "bad color count");
+ static_assert( NF_KEY_WHITE - NF_KEY_COLOR + 1 == 11, "bad color sequence count");
+
+ nStandardPrec = 2;
+
+ Reset();
+}
+
+ImpSvNumberformatScan::~ImpSvNumberformatScan()
+{
+ Reset();
+}
+
+void ImpSvNumberformatScan::ChangeIntl( KeywordLocalization eKeywordLocalization )
+{
+ meKeywordLocalization = eKeywordLocalization;
+ bKeywordsNeedInit = true;
+ bCompatCurNeedInit = true;
+ // may be initialized by InitSpecialKeyword()
+ sKeyword[NF_KEY_TRUE].clear();
+ sKeyword[NF_KEY_FALSE].clear();
+}
+
+void ImpSvNumberformatScan::InitSpecialKeyword( NfKeywordIndex eIdx ) const
+{
+ switch ( eIdx )
+ {
+ case NF_KEY_TRUE :
+ const_cast<ImpSvNumberformatScan*>(this)->sKeyword[NF_KEY_TRUE] =
+ pFormatter->GetCharClass()->uppercase( pFormatter->GetLocaleData()->getTrueWord() );
+ if ( sKeyword[NF_KEY_TRUE].isEmpty() )
+ {
+ SAL_WARN( "svl.numbers", "InitSpecialKeyword: TRUE_WORD?" );
+ const_cast<ImpSvNumberformatScan*>(this)->sKeyword[NF_KEY_TRUE] = sEnglishKeyword[NF_KEY_TRUE];
+ }
+ break;
+ case NF_KEY_FALSE :
+ const_cast<ImpSvNumberformatScan*>(this)->sKeyword[NF_KEY_FALSE] =
+ pFormatter->GetCharClass()->uppercase( pFormatter->GetLocaleData()->getFalseWord() );
+ if ( sKeyword[NF_KEY_FALSE].isEmpty() )
+ {
+ SAL_WARN( "svl.numbers", "InitSpecialKeyword: FALSE_WORD?" );
+ const_cast<ImpSvNumberformatScan*>(this)->sKeyword[NF_KEY_FALSE] = sEnglishKeyword[NF_KEY_FALSE];
+ }
+ break;
+ default:
+ SAL_WARN( "svl.numbers", "InitSpecialKeyword: unknown request" );
+ }
+}
+
+void ImpSvNumberformatScan::InitCompatCur() const
+{
+ ImpSvNumberformatScan* pThis = const_cast<ImpSvNumberformatScan*>(this);
+ // currency symbol for old style ("automatic") compatibility format codes
+ pFormatter->GetCompatibilityCurrency( pThis->sCurSymbol, pThis->sCurAbbrev );
+ // currency symbol upper case
+ pThis->sCurString = pFormatter->GetCharClass()->uppercase( sCurSymbol );
+ bCompatCurNeedInit = false;
+}
+
+void ImpSvNumberformatScan::InitKeywords() const
+{
+ if ( !bKeywordsNeedInit )
+ return ;
+ const_cast<ImpSvNumberformatScan*>(this)->SetDependentKeywords();
+ bKeywordsNeedInit = false;
+}
+
+/** Extract the name of General, Standard, Whatever, ignoring leading modifiers
+ such as [NatNum1]. */
+static OUString lcl_extractStandardGeneralName( const OUString & rCode )
+{
+ OUString aStr;
+ const sal_Unicode* p = rCode.getStr();
+ const sal_Unicode* const pStop = p + rCode.getLength();
+ const sal_Unicode* pBeg = p; // name begins here
+ bool bMod = false;
+ bool bDone = false;
+ while (p < pStop && !bDone)
+ {
+ switch (*p)
+ {
+ case '[':
+ bMod = true;
+ break;
+ case ']':
+ if (bMod)
+ {
+ bMod = false;
+ pBeg = p+1;
+ }
+ // else: would be a locale data error, easily to be spotted in
+ // UI dialog
+ break;
+ case ';':
+ if (!bMod)
+ {
+ bDone = true;
+ --p; // put back, increment by one follows
+ }
+ break;
+ }
+ ++p;
+ if (bMod)
+ {
+ pBeg = p;
+ }
+ }
+ if (pBeg < p)
+ {
+ aStr = rCode.copy( pBeg - rCode.getStr(), p - pBeg);
+ }
+ return aStr;
+}
+
+void ImpSvNumberformatScan::SetDependentKeywords()
+{
+ using namespace ::com::sun::star;
+ using namespace ::com::sun::star::uno;
+
+ const CharClass* pCharClass = pFormatter->GetCharClass();
+ const LocaleDataWrapper* pLocaleData = pFormatter->GetLocaleData();
+ // #80023# be sure to generate keywords for the loaded Locale, not for the
+ // requested Locale, otherwise number format codes might not match
+ const LanguageTag& rLoadedLocale = pLocaleData->getLoadedLanguageTag();
+ LanguageType eLang = rLoadedLocale.getLanguageType( false);
+
+ bool bL10n = (meKeywordLocalization != KeywordLocalization::EnglishOnly);
+ if (bL10n)
+ {
+ // Check if this actually is a locale that uses any localized keywords,
+ // if not then disable localized keywords completely.
+ if ( !eLang.anyOf( LANGUAGE_GERMAN,
+ LANGUAGE_GERMAN_SWISS,
+ LANGUAGE_GERMAN_AUSTRIAN,
+ LANGUAGE_GERMAN_LUXEMBOURG,
+ LANGUAGE_GERMAN_LIECHTENSTEIN,
+ LANGUAGE_DUTCH,
+ LANGUAGE_DUTCH_BELGIAN,
+ LANGUAGE_FRENCH,
+ LANGUAGE_FRENCH_BELGIAN,
+ LANGUAGE_FRENCH_CANADIAN,
+ LANGUAGE_FRENCH_SWISS,
+ LANGUAGE_FRENCH_LUXEMBOURG,
+ LANGUAGE_FRENCH_MONACO,
+ LANGUAGE_FINNISH,
+ LANGUAGE_ITALIAN,
+ LANGUAGE_ITALIAN_SWISS,
+ LANGUAGE_DANISH,
+ LANGUAGE_NORWEGIAN,
+ LANGUAGE_NORWEGIAN_BOKMAL,
+ LANGUAGE_NORWEGIAN_NYNORSK,
+ LANGUAGE_SWEDISH,
+ LANGUAGE_SWEDISH_FINLAND,
+ LANGUAGE_PORTUGUESE,
+ LANGUAGE_PORTUGUESE_BRAZILIAN,
+ LANGUAGE_SPANISH_MODERN,
+ LANGUAGE_SPANISH_DATED,
+ LANGUAGE_SPANISH_MEXICAN,
+ LANGUAGE_SPANISH_GUATEMALA,
+ LANGUAGE_SPANISH_COSTARICA,
+ LANGUAGE_SPANISH_PANAMA,
+ LANGUAGE_SPANISH_DOMINICAN_REPUBLIC,
+ LANGUAGE_SPANISH_VENEZUELA,
+ LANGUAGE_SPANISH_COLOMBIA,
+ LANGUAGE_SPANISH_PERU,
+ LANGUAGE_SPANISH_ARGENTINA,
+ LANGUAGE_SPANISH_ECUADOR,
+ LANGUAGE_SPANISH_CHILE,
+ LANGUAGE_SPANISH_URUGUAY,
+ LANGUAGE_SPANISH_PARAGUAY,
+ LANGUAGE_SPANISH_BOLIVIA,
+ LANGUAGE_SPANISH_EL_SALVADOR,
+ LANGUAGE_SPANISH_HONDURAS,
+ LANGUAGE_SPANISH_NICARAGUA,
+ LANGUAGE_SPANISH_PUERTO_RICO ))
+ {
+ bL10n = false;
+ meKeywordLocalization = KeywordLocalization::EnglishOnly;
+ }
+ }
+
+ // Init the current NfKeywordTable with English keywords.
+ sKeyword = sEnglishKeyword;
+
+ // Set the uppercase localized General name, e.g. Standard -> STANDARD
+ i18n::NumberFormatCode aFormat = xNFC->getFormatCode( NF_NUMBER_STANDARD, rLoadedLocale.getLocale() );
+ sNameStandardFormat = lcl_extractStandardGeneralName( aFormat.Code );
+ sKeyword[NF_KEY_GENERAL] = pCharClass->uppercase( sNameStandardFormat );
+
+ // Thai T NatNum special. Other locale's small letter 't' results in upper
+ // case comparison not matching but length does in conversion mode. Ugly.
+ if (eLang == LANGUAGE_THAI)
+ {
+ sKeyword[NF_KEY_THAI_T] = "T";
+ }
+ else
+ {
+ sKeyword[NF_KEY_THAI_T] = sEnglishKeyword[NF_KEY_THAI_T];
+ }
+
+ // boolean keywords
+ InitSpecialKeyword( NF_KEY_TRUE );
+ InitSpecialKeyword( NF_KEY_FALSE );
+
+ // Boolean equivalent format codes that are written to Excel files, may
+ // have been written to ODF as well, specifically if such loaded Excel file
+ // was saved as ODF, and shall result in proper Boolean again.
+ // "TRUE";"TRUE";"FALSE"
+ sBooleanEquivalent1 = "\"" + sKeyword[NF_KEY_TRUE] + "\";\"" +
+ sKeyword[NF_KEY_TRUE] + "\";\"" + sKeyword[NF_KEY_FALSE] + "\"";
+ // [>0]"TRUE";[<0]"TRUE";"FALSE"
+ sBooleanEquivalent2 = "[>0]\"" + sKeyword[NF_KEY_TRUE] + "\";[<0]\"" +
+ sKeyword[NF_KEY_TRUE] + "\";\"" + sKeyword[NF_KEY_FALSE] + "\"";
+
+ // compatibility currency strings
+ InitCompatCur();
+
+ if (!bL10n)
+ return;
+
+ // All locale dependent keywords overrides follow.
+
+ if ( eLang.anyOf(
+ LANGUAGE_GERMAN,
+ LANGUAGE_GERMAN_SWISS,
+ LANGUAGE_GERMAN_AUSTRIAN,
+ LANGUAGE_GERMAN_LUXEMBOURG,
+ LANGUAGE_GERMAN_LIECHTENSTEIN))
+ {
+ //! all capital letters
+ sKeyword[NF_KEY_M] = "M"; // month 1
+ sKeyword[NF_KEY_MM] = "MM"; // month 01
+ sKeyword[NF_KEY_MMM] = "MMM"; // month Jan
+ sKeyword[NF_KEY_MMMM] = "MMMM"; // month Januar
+ sKeyword[NF_KEY_MMMMM] = "MMMMM"; // month J
+ sKeyword[NF_KEY_H] = "H"; // hour 2
+ sKeyword[NF_KEY_HH] = "HH"; // hour 02
+ sKeyword[NF_KEY_D] = "T";
+ sKeyword[NF_KEY_DD] = "TT";
+ sKeyword[NF_KEY_DDD] = "TTT";
+ sKeyword[NF_KEY_DDDD] = "TTTT";
+ sKeyword[NF_KEY_YY] = "JJ";
+ sKeyword[NF_KEY_YYYY] = "JJJJ";
+ sKeyword[NF_KEY_BOOLEAN] = "LOGISCH";
+ sKeyword[NF_KEY_COLOR] = GermanColorName(NF_KEY_COLOR - NF_KEY_COLOR);
+ sKeyword[NF_KEY_BLACK] = GermanColorName(NF_KEY_BLACK - NF_KEY_COLOR);
+ sKeyword[NF_KEY_BLUE] = GermanColorName(NF_KEY_BLUE - NF_KEY_COLOR);
+ sKeyword[NF_KEY_GREEN] = GermanColorName(NF_KEY_GREEN - NF_KEY_COLOR);
+ sKeyword[NF_KEY_CYAN] = GermanColorName(NF_KEY_CYAN - NF_KEY_COLOR);
+ sKeyword[NF_KEY_RED] = GermanColorName(NF_KEY_RED - NF_KEY_COLOR);
+ sKeyword[NF_KEY_MAGENTA] = GermanColorName(NF_KEY_MAGENTA - NF_KEY_COLOR);
+ sKeyword[NF_KEY_BROWN] = GermanColorName(NF_KEY_BROWN - NF_KEY_COLOR);
+ sKeyword[NF_KEY_GREY] = GermanColorName(NF_KEY_GREY - NF_KEY_COLOR);
+ sKeyword[NF_KEY_YELLOW] = GermanColorName(NF_KEY_YELLOW - NF_KEY_COLOR);
+ sKeyword[NF_KEY_WHITE] = GermanColorName(NF_KEY_WHITE - NF_KEY_COLOR);
+ }
+ else
+ {
+ // day
+ if ( eLang.anyOf(
+ LANGUAGE_ITALIAN,
+ LANGUAGE_ITALIAN_SWISS))
+ {
+ sKeyword[NF_KEY_D] = "G";
+ sKeyword[NF_KEY_DD] = "GG";
+ sKeyword[NF_KEY_DDD] = "GGG";
+ sKeyword[NF_KEY_DDDD] = "GGGG";
+ // must exchange the era code, same as Xcl
+ sKeyword[NF_KEY_G] = "X";
+ sKeyword[NF_KEY_GG] = "XX";
+ sKeyword[NF_KEY_GGG] = "XXX";
+ }
+ else if ( eLang.anyOf(
+ LANGUAGE_FRENCH,
+ LANGUAGE_FRENCH_BELGIAN,
+ LANGUAGE_FRENCH_CANADIAN,
+ LANGUAGE_FRENCH_SWISS,
+ LANGUAGE_FRENCH_LUXEMBOURG,
+ LANGUAGE_FRENCH_MONACO))
+ {
+ sKeyword[NF_KEY_D] = "J";
+ sKeyword[NF_KEY_DD] = "JJ";
+ sKeyword[NF_KEY_DDD] = "JJJ";
+ sKeyword[NF_KEY_DDDD] = "JJJJ";
+ }
+ else if ( eLang == LANGUAGE_FINNISH )
+ {
+ sKeyword[NF_KEY_D] = "P";
+ sKeyword[NF_KEY_DD] = "PP";
+ sKeyword[NF_KEY_DDD] = "PPP";
+ sKeyword[NF_KEY_DDDD] = "PPPP";
+ }
+
+ // month
+ if ( eLang == LANGUAGE_FINNISH )
+ {
+ sKeyword[NF_KEY_M] = "K";
+ sKeyword[NF_KEY_MM] = "KK";
+ sKeyword[NF_KEY_MMM] = "KKK";
+ sKeyword[NF_KEY_MMMM] = "KKKK";
+ sKeyword[NF_KEY_MMMMM] = "KKKKK";
+ }
+
+ // year
+ if ( eLang.anyOf(
+ LANGUAGE_ITALIAN,
+ LANGUAGE_ITALIAN_SWISS,
+ LANGUAGE_FRENCH,
+ LANGUAGE_FRENCH_BELGIAN,
+ LANGUAGE_FRENCH_CANADIAN,
+ LANGUAGE_FRENCH_SWISS,
+ LANGUAGE_FRENCH_LUXEMBOURG,
+ LANGUAGE_FRENCH_MONACO,
+ LANGUAGE_PORTUGUESE,
+ LANGUAGE_PORTUGUESE_BRAZILIAN,
+ LANGUAGE_SPANISH_MODERN,
+ LANGUAGE_SPANISH_DATED,
+ LANGUAGE_SPANISH_MEXICAN,
+ LANGUAGE_SPANISH_GUATEMALA,
+ LANGUAGE_SPANISH_COSTARICA,
+ LANGUAGE_SPANISH_PANAMA,
+ LANGUAGE_SPANISH_DOMINICAN_REPUBLIC,
+ LANGUAGE_SPANISH_VENEZUELA,
+ LANGUAGE_SPANISH_COLOMBIA,
+ LANGUAGE_SPANISH_PERU,
+ LANGUAGE_SPANISH_ARGENTINA,
+ LANGUAGE_SPANISH_ECUADOR,
+ LANGUAGE_SPANISH_CHILE,
+ LANGUAGE_SPANISH_URUGUAY,
+ LANGUAGE_SPANISH_PARAGUAY,
+ LANGUAGE_SPANISH_BOLIVIA,
+ LANGUAGE_SPANISH_EL_SALVADOR,
+ LANGUAGE_SPANISH_HONDURAS,
+ LANGUAGE_SPANISH_NICARAGUA,
+ LANGUAGE_SPANISH_PUERTO_RICO))
+ {
+ sKeyword[NF_KEY_YY] = "AA";
+ sKeyword[NF_KEY_YYYY] = "AAAA";
+ // must exchange the day of week name code, same as Xcl
+ sKeyword[NF_KEY_AAA] = "OOO";
+ sKeyword[NF_KEY_AAAA] = "OOOO";
+ }
+ else if ( eLang.anyOf(
+ LANGUAGE_DUTCH,
+ LANGUAGE_DUTCH_BELGIAN))
+ {
+ sKeyword[NF_KEY_YY] = "JJ";
+ sKeyword[NF_KEY_YYYY] = "JJJJ";
+ }
+ else if ( eLang == LANGUAGE_FINNISH )
+ {
+ sKeyword[NF_KEY_YY] = "VV";
+ sKeyword[NF_KEY_YYYY] = "VVVV";
+ }
+
+ // hour
+ if ( eLang.anyOf(
+ LANGUAGE_DUTCH,
+ LANGUAGE_DUTCH_BELGIAN))
+ {
+ sKeyword[NF_KEY_H] = "U";
+ sKeyword[NF_KEY_HH] = "UU";
+ }
+ else if ( eLang.anyOf(
+ LANGUAGE_FINNISH,
+ LANGUAGE_SWEDISH,
+ LANGUAGE_SWEDISH_FINLAND,
+ LANGUAGE_DANISH,
+ LANGUAGE_NORWEGIAN,
+ LANGUAGE_NORWEGIAN_BOKMAL,
+ LANGUAGE_NORWEGIAN_NYNORSK))
+ {
+ sKeyword[NF_KEY_H] = "T";
+ sKeyword[NF_KEY_HH] = "TT";
+ }
+ }
+}
+
+void ImpSvNumberformatScan::ChangeNullDate(sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear)
+{
+ maNullDate = Date(nDay, nMonth, nYear);
+ if (!maNullDate.IsValidDate())
+ {
+ maNullDate.Normalize();
+ SAL_WARN("svl.numbers","ImpSvNumberformatScan::ChangeNullDate - not valid"
+ " d: " << nDay << " m: " << nMonth << " y: " << nYear << " normalized to"
+ " d: " << maNullDate.GetDay() << " m: " << maNullDate.GetMonth() << " y: " << maNullDate.GetYear());
+ }
+}
+
+void ImpSvNumberformatScan::ChangeStandardPrec(sal_uInt16 nPrec)
+{
+ nStandardPrec = nPrec;
+}
+
+const Color* ImpSvNumberformatScan::GetColor(OUString& sStr) const
+{
+ OUString sString = pFormatter->GetCharClass()->uppercase(sStr);
+ const NfKeywordTable & rKeyword = GetKeywords();
+ size_t i = 0;
+ while (i < NF_MAX_DEFAULT_COLORS && sString != rKeyword[NF_KEY_FIRSTCOLOR+i] )
+ {
+ i++;
+ }
+ if (i >= NF_MAX_DEFAULT_COLORS && meKeywordLocalization == KeywordLocalization::AllowEnglish)
+ {
+ LanguageType eLang = pFormatter->GetLocaleData()->getLoadedLanguageTag().getLanguageType( false);
+ if ( eLang.anyOf(
+ LANGUAGE_GERMAN,
+ LANGUAGE_GERMAN_SWISS,
+ LANGUAGE_GERMAN_AUSTRIAN,
+ LANGUAGE_GERMAN_LUXEMBOURG,
+ LANGUAGE_GERMAN_LIECHTENSTEIN )) // only German uses localized color names
+ {
+ size_t j = 0;
+ while ( j < NF_MAX_DEFAULT_COLORS && sString != sEnglishKeyword[NF_KEY_FIRSTCOLOR + j] )
+ {
+ ++j;
+ }
+ if ( j < NF_MAX_DEFAULT_COLORS )
+ {
+ i = j;
+ }
+ }
+ }
+
+ enum ColorKeywordConversion
+ {
+ None,
+ GermanToEnglish,
+ EnglishToGerman
+ } eColorKeywordConversion(None);
+
+ if (bConvertMode)
+ {
+ const bool bFromGerman = eTmpLnge.anyOf(
+ LANGUAGE_GERMAN,
+ LANGUAGE_GERMAN_SWISS,
+ LANGUAGE_GERMAN_AUSTRIAN,
+ LANGUAGE_GERMAN_LUXEMBOURG,
+ LANGUAGE_GERMAN_LIECHTENSTEIN);
+ const bool bToGerman = eNewLnge.anyOf(
+ LANGUAGE_GERMAN,
+ LANGUAGE_GERMAN_SWISS,
+ LANGUAGE_GERMAN_AUSTRIAN,
+ LANGUAGE_GERMAN_LUXEMBOURG,
+ LANGUAGE_GERMAN_LIECHTENSTEIN);
+ if (bFromGerman && !bToGerman)
+ eColorKeywordConversion = ColorKeywordConversion::GermanToEnglish;
+ else if (!bFromGerman && bToGerman)
+ eColorKeywordConversion = ColorKeywordConversion::EnglishToGerman;
+ }
+
+ const Color* pResult = nullptr;
+ if (i >= NF_MAX_DEFAULT_COLORS)
+ {
+ const OUString& rColorWord = rKeyword[NF_KEY_COLOR];
+ bool bL10n = true;
+ if ((bL10n = sString.startsWith(rColorWord)) ||
+ ((meKeywordLocalization == KeywordLocalization::AllowEnglish) &&
+ sString.startsWith(sEnglishKeyword[NF_KEY_COLOR])))
+ {
+ sal_Int32 nPos = (bL10n ? rColorWord.getLength() : sEnglishKeyword[NF_KEY_COLOR].getLength());
+ sStr = sStr.copy(nPos);
+ sStr = comphelper::string::strip(sStr, ' ');
+ switch (eColorKeywordConversion)
+ {
+ case ColorKeywordConversion::None:
+ sStr = rColorWord + sStr;
+ break;
+ case ColorKeywordConversion::GermanToEnglish:
+ sStr = sEnglishKeyword[NF_KEY_COLOR] + sStr; // Farbe -> COLOR
+ break;
+ case ColorKeywordConversion::EnglishToGerman:
+ sStr = GermanColorName(NF_KEY_COLOR - NF_KEY_COLOR) + sStr; // Color -> FARBE
+ break;
+ }
+ sString = sString.copy(nPos);
+ sString = comphelper::string::strip(sString, ' ');
+
+ if ( CharClass::isAsciiNumeric( sString ) )
+ {
+ sal_Int32 nIndex = sString.toInt32();
+ if (nIndex > 0 && nIndex <= 64)
+ {
+ pResult = pFormatter->GetUserDefColor(static_cast<sal_uInt16>(nIndex)-1);
+ }
+ }
+ }
+ }
+ else
+ {
+ sStr.clear();
+ switch (eColorKeywordConversion)
+ {
+ case ColorKeywordConversion::None:
+ sStr = rKeyword[NF_KEY_FIRSTCOLOR+i];
+ break;
+ case ColorKeywordConversion::GermanToEnglish:
+ sStr = sEnglishKeyword[NF_KEY_FIRSTCOLOR + i]; // Rot -> RED
+ break;
+ case ColorKeywordConversion::EnglishToGerman:
+ sStr = GermanColorName(NF_KEY_FIRSTCOLOR - NF_KEY_COLOR + i); // Red -> ROT
+ break;
+ }
+ pResult = &(StandardColor[i]);
+ }
+ return pResult;
+}
+
+short ImpSvNumberformatScan::GetKeyWord( const OUString& sSymbol, sal_Int32 nPos, bool& rbFoundEnglish ) const
+{
+ OUString sString = pFormatter->GetCharClass()->uppercase( sSymbol, nPos, sSymbol.getLength() - nPos );
+ const NfKeywordTable & rKeyword = GetKeywords();
+ // #77026# for the Xcl perverts: the GENERAL keyword is recognized anywhere
+ if (sString.startsWith( rKeyword[NF_KEY_GENERAL] ))
+ {
+ return NF_KEY_GENERAL;
+ }
+ if ((meKeywordLocalization == KeywordLocalization::AllowEnglish) &&
+ sString.startsWith( sEnglishKeyword[NF_KEY_GENERAL]))
+ {
+ rbFoundEnglish = true;
+ return NF_KEY_GENERAL;
+ }
+
+ // MUST be a reverse search to find longer strings first,
+ // new keywords take precedence over old keywords,
+ // skip colors et al after keywords.
+ short i = NF_KEY_LASTKEYWORD;
+ while (i > 0 && !sString.startsWith( rKeyword[i]))
+ {
+ i--;
+ }
+ if (i == 0 && meKeywordLocalization == KeywordLocalization::AllowEnglish)
+ {
+ // No localized (if so) keyword, try English keywords if keywords
+ // are localized. That was already checked in SetDependentKeywords().
+ i = NF_KEY_LASTKEYWORD;
+ while (i > 0 && !sString.startsWith( sEnglishKeyword[i]))
+ {
+ i--;
+ }
+ }
+
+ // The Thai T NatNum modifier during Xcl import.
+ if (i == 0 && bConvertMode &&
+ sString[0] == 'T' &&
+ eTmpLnge == LANGUAGE_ENGLISH_US &&
+ MsLangId::getRealLanguage( eNewLnge) == LANGUAGE_THAI)
+ {
+ i = NF_KEY_THAI_T;
+ }
+ return i; // 0 => not found
+}
+
+/**
+ * Next_Symbol
+ *
+ * Splits up the input for further processing (by the Turing machine).
+ *
+ * Starting state = SsStar
+ *
+ * ---------------+-------------------+---------------------------+---------------
+ * Old state | Character read | Event | New state
+ * ---------------+-------------------+---------------------------+---------------
+ * SsStart | Character | Symbol = Character | SsGetWord
+ * | " | Type = String | SsGetString
+ * | \ | Type = String | SsGetChar
+ * | * | Type = Star | SsGetStar
+ * | _ | Type = Blank | SsGetBlank
+ * | @ # 0 ? / . , % [ | Symbol = Character; |
+ * | ] ' Blank | Type = Control character | SsStop
+ * | $ - + ( ) : | Type = String; |
+ * | Else | Symbol = Character | SsStop
+ * ---------------|-------------------+---------------------------+---------------
+ * SsGetChar | Else | Symbol = Character | SsStop
+ * ---------------+-------------------+---------------------------+---------------
+ * GetString | " | | SsStop
+ * | Else | Symbol += Character | GetString
+ * ---------------+-------------------+---------------------------+---------------
+ * SsGetWord | Character | Symbol += Character |
+ * | + - (E+ E-)| Symbol += Character | SsStop
+ * | / (AM/PM)| Symbol += Character |
+ * | Else | Pos--, if Key Type = Word | SsStop
+ * ---------------+-------------------+---------------------------+---------------
+ * SsGetStar | Else | Symbol += Character | SsStop
+ * | | Mark special case * |
+ * ---------------+-------------------+---------------------------+---------------
+ * SsGetBlank | Else | Symbol + =Character | SsStop
+ * | | Mark special case _ |
+ * ---------------------------------------------------------------+--------------
+ *
+ * If we recognize a keyword in the state SsGetWord (even as the symbol's start text)
+ * we write back the rest of the characters!
+ */
+
+namespace {
+
+enum ScanState
+{
+ SsStop = 0,
+ SsStart = 1,
+ SsGetChar = 2,
+ SsGetString = 3,
+ SsGetWord = 4,
+ SsGetStar = 5,
+ SsGetBlank = 6
+};
+
+}
+
+short ImpSvNumberformatScan::Next_Symbol( const OUString& rStr,
+ sal_Int32& nPos,
+ OUString& sSymbol ) const
+{
+ InitKeywords();
+ const CharClass* pChrCls = pFormatter->GetCharClass();
+ const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData();
+ short eType = 0;
+ ScanState eState = SsStart;
+ OUStringBuffer sSymbolBuffer;
+ while ( nPos < rStr.getLength() && eState != SsStop )
+ {
+ sal_Unicode cToken = rStr[nPos++];
+ switch (eState)
+ {
+ case SsStart:
+ // Fetch any currency longer than one character and don't get
+ // confused later on by "E/" or other combinations of letters
+ // and meaningful symbols. Necessary for old automatic currency.
+ // #96158# But don't do it if we're starting a "[...]" section,
+ // for example a "[$...]" new currency symbol to not parse away
+ // "$U" (symbol) of "[$UYU]" (abbreviation).
+ if ( nCurrPos >= 0 && sCurString.getLength() > 1 &&
+ nPos-1 + sCurString.getLength() <= rStr.getLength() &&
+ (nPos <= 1 || rStr[nPos-2] != '[') )
+ {
+ OUString aTest = pChrCls->uppercase( rStr.copy( nPos-1, sCurString.getLength() ) );
+ if ( aTest == sCurString )
+ {
+ sSymbol = rStr.copy( --nPos, sCurString.getLength() );
+ nPos = nPos + sSymbol.getLength();
+ eType = NF_SYMBOLTYPE_STRING;
+ return eType;
+ }
+ }
+ switch (cToken)
+ {
+ case '#':
+ case '0':
+ case '?':
+ case '%':
+ case '@':
+ case '[':
+ case ']':
+ case ',':
+ case '.':
+ case '/':
+ case '\'':
+ case ' ':
+ case ':':
+ case '-':
+ eType = NF_SYMBOLTYPE_DEL;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ eState = SsStop;
+ break;
+ case '*':
+ eType = NF_SYMBOLTYPE_STAR;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ eState = SsGetStar;
+ break;
+ case '_':
+ eType = NF_SYMBOLTYPE_BLANK;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ eState = SsGetBlank;
+ break;
+ case '"':
+ eType = NF_SYMBOLTYPE_STRING;
+ eState = SsGetString;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ break;
+ case '\\':
+ eType = NF_SYMBOLTYPE_STRING;
+ eState = SsGetChar;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ break;
+ case '$':
+ case '+':
+ case '(':
+ case ')':
+ eType = NF_SYMBOLTYPE_STRING;
+ eState = SsStop;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ break;
+ default :
+ if (StringEqualsChar( pFormatter->GetNumDecimalSep(), cToken) ||
+ StringEqualsChar( pFormatter->GetNumThousandSep(), cToken) ||
+ StringEqualsChar( pFormatter->GetDateSep(), cToken) ||
+ StringEqualsChar( pLoc->getTimeSep(), cToken) ||
+ StringEqualsChar( pLoc->getTime100SecSep(), cToken))
+ {
+ // Another separator than pre-known ASCII
+ eType = NF_SYMBOLTYPE_DEL;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ eState = SsStop;
+ }
+ else if ( pChrCls->isLetter( rStr, nPos-1 ) )
+ {
+ bool bFoundEnglish = false;
+ short nTmpType = GetKeyWord( rStr, nPos-1, bFoundEnglish);
+ if ( nTmpType )
+ {
+ bool bCurrency = false;
+ // "Automatic" currency may start with keyword,
+ // like "R" (Rand) and 'R' (era)
+ if ( nCurrPos >= 0 &&
+ nPos-1 + sCurString.getLength() <= rStr.getLength() &&
+ sCurString.startsWith( bFoundEnglish ? sEnglishKeyword[nTmpType] : sKeyword[nTmpType]))
+ {
+ OUString aTest = pChrCls->uppercase( rStr.copy( nPos-1, sCurString.getLength() ) );
+ if ( aTest == sCurString )
+ {
+ bCurrency = true;
+ }
+ }
+ if ( bCurrency )
+ {
+ eState = SsGetWord;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ }
+ else
+ {
+ eType = nTmpType;
+ // The code to be advanced is the detected keyword,
+ // not necessarily the locale's keyword, but the
+ // symbol is to be the locale's keyword.
+ sal_Int32 nLen;
+ if (bFoundEnglish)
+ {
+ nLen = sEnglishKeyword[eType].getLength();
+ // Use the locale's General keyword name, not uppercase.
+ sSymbolBuffer = (eType == NF_KEY_GENERAL ? sNameStandardFormat : sKeyword[eType]);
+ }
+ else
+ {
+ nLen = sKeyword[eType].getLength();
+ // Preserve a locale's keyword's case as entered.
+ sSymbolBuffer = rStr.subView( nPos-1, nLen);
+ }
+ if ((eType == NF_KEY_E || IsAmbiguousE(eType)) && nPos < rStr.getLength())
+ {
+ sal_Unicode cNext = rStr[nPos];
+ switch ( cNext )
+ {
+ case '+' :
+ case '-' : // E+ E- combine to one symbol
+ sSymbolBuffer.append(OUStringChar(cNext));
+ eType = NF_KEY_E;
+ nPos++;
+ break;
+ case '0' :
+ case '#' : // scientific E without sign
+ eType = NF_KEY_E;
+ break;
+ }
+ }
+ nPos--;
+ nPos = nPos + nLen;
+ eState = SsStop;
+ }
+ }
+ else
+ {
+ eState = SsGetWord;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ }
+ }
+ else
+ {
+ eType = NF_SYMBOLTYPE_STRING;
+ eState = SsStop;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ }
+ break;
+ }
+ break;
+ case SsGetChar:
+ sSymbolBuffer.append(OUStringChar(cToken));
+ eState = SsStop;
+ break;
+ case SsGetString:
+ if (cToken == '"')
+ {
+ eState = SsStop;
+ }
+ sSymbolBuffer.append(OUStringChar(cToken));
+ break;
+ case SsGetWord:
+ if ( pChrCls->isLetter( rStr, nPos-1 ) )
+ {
+ bool bFoundEnglish = false;
+ short nTmpType = GetKeyWord( rStr, nPos-1, bFoundEnglish);
+ if ( nTmpType )
+ {
+ // beginning of keyword, stop scan and put back
+ eType = NF_SYMBOLTYPE_STRING;
+ eState = SsStop;
+ nPos--;
+ }
+ else
+ {
+ sSymbolBuffer.append(OUStringChar(cToken));
+ }
+ }
+ else
+ {
+ bool bDontStop = false;
+ sal_Unicode cNext;
+ switch (cToken)
+ {
+ case '/': // AM/PM, A/P
+ if (nPos < rStr.getLength())
+ {
+ cNext = rStr[nPos];
+ if ( cNext == 'P' || cNext == 'p' )
+ {
+ sal_Int32 nLen = sSymbolBuffer.getLength();
+ if ( 1 <= nLen &&
+ (sSymbolBuffer[0] == 'A' || sSymbolBuffer[0] == 'a') &&
+ (nLen == 1 ||
+ (nLen == 2 && (sSymbolBuffer[1] == 'M' || sSymbolBuffer[1] == 'm')
+ && (rStr[nPos + 1] == 'M' || rStr[nPos + 1] == 'm'))))
+ {
+ sSymbolBuffer.append(OUStringChar(cToken));
+ bDontStop = true;
+ }
+ }
+ }
+ break;
+ }
+ // anything not recognized will stop the scan
+ if (!bDontStop)
+ {
+ eState = SsStop;
+ nPos--;
+ eType = NF_SYMBOLTYPE_STRING;
+ }
+ }
+ break;
+ case SsGetStar:
+ eState = SsStop;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ break;
+ case SsGetBlank:
+ eState = SsStop;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ break;
+ default:
+ break;
+ } // of switch
+ } // of while
+ if (eState == SsGetWord)
+ {
+ eType = NF_SYMBOLTYPE_STRING;
+ }
+ sSymbol = sSymbolBuffer.makeStringAndClear();
+ return eType;
+}
+
+sal_Int32 ImpSvNumberformatScan::Symbol_Division(const OUString& rString)
+{
+ nCurrPos = -1;
+ // Do we have some sort of currency?
+ OUString sString = pFormatter->GetCharClass()->uppercase(rString);
+ sal_Int32 nCPos = 0;
+ while (nCPos >= 0 && nCPos < sString.getLength())
+ {
+ nCPos = sString.indexOf(GetCurString(),nCPos);
+ if (nCPos >= 0)
+ {
+ // In Quotes?
+ sal_Int32 nQ = SvNumberformat::GetQuoteEnd( sString, nCPos );
+ if ( nQ < 0 )
+ {
+ sal_Unicode c;
+ if ( nCPos == 0 ||
+ ((c = sString[nCPos-1]) != '"'
+ && c != '\\') ) // dm can be protected by "dm \d
+ {
+ nCurrPos = nCPos;
+ nCPos = -1;
+ }
+ else
+ {
+ nCPos++; // Continue search
+ }
+ }
+ else
+ {
+ nCPos = nQ + 1; // Continue search
+ }
+ }
+ }
+ nStringsCnt = 0;
+ bool bStar = false; // Is set on detecting '*'
+ Reset();
+
+ sal_Int32 nPos = 0;
+ const sal_Int32 nLen = rString.getLength();
+ while (nPos < nLen && nStringsCnt < NF_MAX_FORMAT_SYMBOLS)
+ {
+ nTypeArray[nStringsCnt] = Next_Symbol(rString, nPos, sStrArray[nStringsCnt]);
+ if (nTypeArray[nStringsCnt] == NF_SYMBOLTYPE_STAR)
+ { // Monitoring the '*'
+ if (bStar)
+ {
+ return nPos; // Error: double '*'
+ }
+ else
+ {
+ // Valid only if there is a character following, else we are
+ // at the end of a code that does not have a fill character
+ // (yet?).
+ if (sStrArray[nStringsCnt].getLength() < 2)
+ return nPos;
+ bStar = true;
+ }
+ }
+ nStringsCnt++;
+ }
+
+ return 0; // 0 => ok
+}
+
+void ImpSvNumberformatScan::SkipStrings(sal_uInt16& i, sal_Int32& nPos) const
+{
+ while (i < nStringsCnt && ( nTypeArray[i] == NF_SYMBOLTYPE_STRING
+ || nTypeArray[i] == NF_SYMBOLTYPE_BLANK
+ || nTypeArray[i] == NF_SYMBOLTYPE_STAR) )
+ {
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+}
+
+sal_uInt16 ImpSvNumberformatScan::PreviousKeyword(sal_uInt16 i) const
+{
+ short res = 0;
+ if (i > 0 && i < nStringsCnt)
+ {
+ i--;
+ while (i > 0 && nTypeArray[i] <= 0)
+ {
+ i--;
+ }
+ if (nTypeArray[i] > 0)
+ {
+ res = nTypeArray[i];
+ }
+ }
+ return res;
+}
+
+sal_uInt16 ImpSvNumberformatScan::NextKeyword(sal_uInt16 i) const
+{
+ short res = 0;
+ if (i < nStringsCnt-1)
+ {
+ i++;
+ while (i < nStringsCnt-1 && nTypeArray[i] <= 0)
+ {
+ i++;
+ }
+ if (nTypeArray[i] > 0)
+ {
+ res = nTypeArray[i];
+ }
+ }
+ return res;
+}
+
+short ImpSvNumberformatScan::PreviousType( sal_uInt16 i ) const
+{
+ if ( i > 0 && i < nStringsCnt )
+ {
+ do
+ {
+ i--;
+ }
+ while ( i > 0 && nTypeArray[i] == NF_SYMBOLTYPE_EMPTY );
+ return nTypeArray[i];
+ }
+ return 0;
+}
+
+sal_Unicode ImpSvNumberformatScan::PreviousChar(sal_uInt16 i) const
+{
+ sal_Unicode res = ' ';
+ if (i > 0 && i < nStringsCnt)
+ {
+ i--;
+ while (i > 0 &&
+ ( nTypeArray[i] == NF_SYMBOLTYPE_EMPTY ||
+ nTypeArray[i] == NF_SYMBOLTYPE_STRING ||
+ nTypeArray[i] == NF_SYMBOLTYPE_STAR ||
+ nTypeArray[i] == NF_SYMBOLTYPE_BLANK ))
+ {
+ i--;
+ }
+ if (sStrArray[i].getLength() > 0)
+ {
+ res = sStrArray[i][sStrArray[i].getLength()-1];
+ }
+ }
+ return res;
+}
+
+sal_Unicode ImpSvNumberformatScan::NextChar(sal_uInt16 i) const
+{
+ sal_Unicode res = ' ';
+ if (i < nStringsCnt-1)
+ {
+ i++;
+ while (i < nStringsCnt-1 &&
+ ( nTypeArray[i] == NF_SYMBOLTYPE_EMPTY ||
+ nTypeArray[i] == NF_SYMBOLTYPE_STRING ||
+ nTypeArray[i] == NF_SYMBOLTYPE_STAR ||
+ nTypeArray[i] == NF_SYMBOLTYPE_BLANK))
+ {
+ i++;
+ }
+ if (sStrArray[i].getLength() > 0)
+ {
+ res = sStrArray[i][0];
+ }
+ }
+ return res;
+}
+
+bool ImpSvNumberformatScan::IsLastBlankBeforeFrac(sal_uInt16 i) const
+{
+ bool res = true;
+ if (i < nStringsCnt-1)
+ {
+ bool bStop = false;
+ i++;
+ while (i < nStringsCnt-1 && !bStop)
+ {
+ i++;
+ if ( nTypeArray[i] == NF_SYMBOLTYPE_DEL &&
+ sStrArray[i][0] == '/')
+ {
+ bStop = true;
+ }
+ else if ( ( nTypeArray[i] == NF_SYMBOLTYPE_DEL &&
+ sStrArray[i][0] == ' ') ||
+ nTypeArray[i] == NF_SYMBOLTYPE_STRING ) // integer/fraction delimiter can also be a string
+ {
+ res = false;
+ }
+ }
+ if (!bStop) // no '/'{
+ {
+ res = false;
+ }
+ }
+ else
+ {
+ res = false; // no '/' any more
+ }
+ return res;
+}
+
+void ImpSvNumberformatScan::Reset()
+{
+ nStringsCnt = 0;
+ nResultStringsCnt = 0;
+ eScannedType = SvNumFormatType::UNDEFINED;
+ bExp = false;
+ bThousand = false;
+ nThousand = 0;
+ bDecSep = false;
+ nDecPos = sal_uInt16(-1);
+ nExpPos = sal_uInt16(-1);
+ nBlankPos = sal_uInt16(-1);
+ nCntPre = 0;
+ nCntPost = 0;
+ nCntExp = 0;
+ bFrac = false;
+ bBlank = false;
+ nNatNumModifier = 0;
+}
+
+bool ImpSvNumberformatScan::Is100SecZero( sal_uInt16 i, bool bHadDecSep ) const
+{
+ sal_uInt16 nIndexPre = PreviousKeyword( i );
+ return (nIndexPre == NF_KEY_S || nIndexPre == NF_KEY_SS) &&
+ (bHadDecSep ||
+ ( i > 0 && nTypeArray[i-1] == NF_SYMBOLTYPE_STRING));
+ // SS"any"00 take "any" as a valid decimal separator
+}
+
+sal_Int32 ImpSvNumberformatScan::ScanType()
+{
+ const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData();
+
+ sal_Int32 nPos = 0;
+ sal_uInt16 i = 0;
+ SvNumFormatType eNewType;
+ bool bMatchBracket = false;
+ bool bHaveGeneral = false; // if General/Standard encountered
+ bool bIsTimeDetected =false; // hour or second found in format
+ bool bHaveMinute = false;
+
+ SkipStrings(i, nPos);
+ while (i < nStringsCnt)
+ {
+ if (nTypeArray[i] > 0)
+ { // keyword
+ sal_uInt16 nIndexPre;
+ sal_uInt16 nIndexNex;
+
+ switch (nTypeArray[i])
+ {
+ case NF_KEY_E: // E
+ eNewType = SvNumFormatType::SCIENTIFIC;
+ break;
+ case NF_KEY_H: // H
+ case NF_KEY_HH: // HH
+ bIsTimeDetected = true;
+ [[fallthrough]];
+ case NF_KEY_S: // S
+ case NF_KEY_SS: // SS
+ if ( !bHaveMinute )
+ bIsTimeDetected = true;
+ [[fallthrough]];
+ case NF_KEY_AMPM: // AM,A,PM,P
+ case NF_KEY_AP:
+ eNewType = SvNumFormatType::TIME;
+ break;
+ case NF_KEY_M: // M
+ case NF_KEY_MM: // MM
+ case NF_KEY_MI: // M minute detected in Finnish
+ case NF_KEY_MMI: // MM
+ /* Minute or month.
+ Minute if one of:
+ * preceded by time keyword H (ignoring separators)
+ * followed by time keyword S (ignoring separators)
+ * H or S was detected and this is the first M following
+ * preceded by '[' amount bracket
+ Else month.
+ That are the Excel rules. BUT, we break it because certainly
+ in something like {HH YYYY-MM-DD} the MM is NOT meant to be
+ minute, so not if MM is between YY and DD or DD and YY.
+ Actually not if any date specific keyword followed a time
+ setting keyword.
+ */
+ nIndexPre = PreviousKeyword(i);
+ nIndexNex = NextKeyword(i);
+ if (nIndexPre == NF_KEY_H || // H
+ nIndexPre == NF_KEY_HH || // HH
+ nIndexNex == NF_KEY_S || // S
+ nIndexNex == NF_KEY_SS || // SS
+ bIsTimeDetected || // tdf#101147
+ PreviousChar(i) == '[' ) // [M
+ {
+ eNewType = SvNumFormatType::TIME;
+ if ( nTypeArray[i] == NF_KEY_M || nTypeArray[i] == NF_KEY_MM )
+ {
+ nTypeArray[i] -= 2; // 6 -> 4, 7 -> 5
+ }
+ bIsTimeDetected = false; // next M should be month
+ bHaveMinute = true;
+ }
+ else
+ {
+ eNewType = SvNumFormatType::DATE;
+ if ( nTypeArray[i] == NF_KEY_MI || nTypeArray[i] == NF_KEY_MMI )
+ { // follow resolution of tdf#33689 for Finnish
+ nTypeArray[i] += 2; // 4 -> 6, 5 -> 7
+ }
+ }
+ break;
+ case NF_KEY_MMM: // MMM
+ case NF_KEY_MMMM: // MMMM
+ case NF_KEY_MMMMM: // MMMMM
+ case NF_KEY_Q: // Q
+ case NF_KEY_QQ: // QQ
+ case NF_KEY_D: // D
+ case NF_KEY_DD: // DD
+ case NF_KEY_DDD: // DDD
+ case NF_KEY_DDDD: // DDDD
+ case NF_KEY_YY: // YY
+ case NF_KEY_YYYY: // YYYY
+ case NF_KEY_NN: // NN
+ case NF_KEY_NNN: // NNN
+ case NF_KEY_NNNN: // NNNN
+ case NF_KEY_WW : // WW
+ case NF_KEY_AAA : // AAA
+ case NF_KEY_AAAA : // AAAA
+ case NF_KEY_EC : // E
+ case NF_KEY_EEC : // EE
+ case NF_KEY_G : // G
+ case NF_KEY_GG : // GG
+ case NF_KEY_GGG : // GGG
+ case NF_KEY_R : // R
+ case NF_KEY_RR : // RR
+ eNewType = SvNumFormatType::DATE;
+ bIsTimeDetected = false;
+ break;
+ case NF_KEY_CCC: // CCC
+ eNewType = SvNumFormatType::CURRENCY;
+ break;
+ case NF_KEY_BOOLEAN: // BOOLEAN
+ eNewType = SvNumFormatType::LOGICAL;
+ break;
+ case NF_KEY_GENERAL: // General
+ eNewType = SvNumFormatType::NUMBER;
+ bHaveGeneral = true;
+ break;
+ default:
+ eNewType = SvNumFormatType::UNDEFINED;
+ break;
+ }
+ }
+ else
+ { // control character
+ switch ( sStrArray[i][0] )
+ {
+ case '#':
+ case '?':
+ eNewType = SvNumFormatType::NUMBER;
+ break;
+ case '0':
+ if ( eScannedType & SvNumFormatType::TIME )
+ {
+ if ( Is100SecZero( i, bDecSep ) )
+ {
+ bDecSep = true; // subsequent 0's
+ eNewType = SvNumFormatType::TIME;
+ }
+ else
+ {
+ return nPos; // Error
+ }
+ }
+ else
+ {
+ eNewType = SvNumFormatType::NUMBER;
+ }
+ break;
+ case '%':
+ eNewType = SvNumFormatType::PERCENT;
+ break;
+ case '/':
+ eNewType = SvNumFormatType::FRACTION;
+ break;
+ case '[':
+ if ( i < nStringsCnt-1 &&
+ nTypeArray[i+1] == NF_SYMBOLTYPE_STRING &&
+ sStrArray[i+1][0] == '$' )
+ {
+ eNewType = SvNumFormatType::CURRENCY;
+ bMatchBracket = true;
+ }
+ else if ( i < nStringsCnt-1 &&
+ nTypeArray[i+1] == NF_SYMBOLTYPE_STRING &&
+ sStrArray[i+1][0] == '~' )
+ {
+ eNewType = SvNumFormatType::DATE;
+ bMatchBracket = true;
+ }
+ else
+ {
+ sal_uInt16 nIndexNex = NextKeyword(i);
+ if (nIndexNex == NF_KEY_H || // H
+ nIndexNex == NF_KEY_HH || // HH
+ nIndexNex == NF_KEY_M || // M
+ nIndexNex == NF_KEY_MM || // MM
+ nIndexNex == NF_KEY_S || // S
+ nIndexNex == NF_KEY_SS ) // SS
+ eNewType = SvNumFormatType::TIME;
+ else
+ {
+ return nPos; // Error
+ }
+ }
+ break;
+ case '@':
+ eNewType = SvNumFormatType::TEXT;
+ break;
+ default:
+ // Separator for SS,0
+ if ((eScannedType & SvNumFormatType::TIME)
+ && 0 < i && (nTypeArray[i-1] == NF_KEY_S || nTypeArray[i-1] == NF_KEY_SS))
+ {
+ // For ISO 8601 only YYYY-MM-DD"T"HH:MM:SS,0 accept both
+ // ',' and '.' regardless of locale's separator, and only
+ // those.
+ // XXX NOTE: this catches only known separators of
+ // NF_SYMBOLTYPE_DEL as all NF_SYMBOLTYPE_STRING are
+ // skipped during the loop. Meant to error out if the
+ // Time100SecSep or decimal separator differ and were used.
+ if ((eScannedType & SvNumFormatType::DATE)
+ && 11 <= i && i < nStringsCnt-1
+ && (nTypeArray[i-6] == NF_SYMBOLTYPE_STRING
+ && (sStrArray[i-6] == "\"T\"" || sStrArray[i-6] == "\\T"
+ || sStrArray[i-6] == "T"))
+ && (nTypeArray[i-11] == NF_KEY_YYYY)
+ && (nTypeArray[i-9] == NF_KEY_M || nTypeArray[i-9] == NF_KEY_MM)
+ && (nTypeArray[i-7] == NF_KEY_D || nTypeArray[i-7] == NF_KEY_DD)
+ && (nTypeArray[i-5] == NF_KEY_H || nTypeArray[i-5] == NF_KEY_HH)
+ && (nTypeArray[i-3] == NF_KEY_MI || nTypeArray[i-3] == NF_KEY_MMI)
+ && (nTypeArray[i+1] == NF_SYMBOLTYPE_DEL && sStrArray[i+1][0] == '0'))
+
+ {
+ if (sStrArray[i].getLength() == 1 && (sStrArray[i][0] == ',' || sStrArray[i][0] == '.'))
+ bDecSep = true;
+ else
+ return nPos; // Error
+ }
+ else if (pLoc->getTime100SecSep() == sStrArray[i])
+ bDecSep = true;
+ }
+ eNewType = SvNumFormatType::UNDEFINED;
+ break;
+ }
+ }
+ if (eScannedType == SvNumFormatType::UNDEFINED)
+ {
+ eScannedType = eNewType;
+ }
+ else if (eScannedType == SvNumFormatType::TEXT || eNewType == SvNumFormatType::TEXT)
+ {
+ eScannedType = SvNumFormatType::TEXT; // Text always remains text
+ }
+ else if (eNewType == SvNumFormatType::UNDEFINED)
+ { // Remains as is
+ }
+ else if (eScannedType != eNewType)
+ {
+ switch (eScannedType)
+ {
+ case SvNumFormatType::DATE:
+ switch (eNewType)
+ {
+ case SvNumFormatType::TIME:
+ eScannedType = SvNumFormatType::DATETIME;
+ break;
+ case SvNumFormatType::FRACTION: // DD/MM
+ break;
+ default:
+ if (nCurrPos >= 0)
+ {
+ eScannedType = SvNumFormatType::UNDEFINED;
+ }
+ else if ( sStrArray[i] != pFormatter->GetDateSep() )
+ {
+ return nPos;
+ }
+ }
+ break;
+ case SvNumFormatType::TIME:
+ switch (eNewType)
+ {
+ case SvNumFormatType::DATE:
+ eScannedType = SvNumFormatType::DATETIME;
+ break;
+ case SvNumFormatType::FRACTION: // MM/SS
+ break;
+ default:
+ if (nCurrPos >= 0)
+ {
+ eScannedType = SvNumFormatType::UNDEFINED;
+ }
+ else if (pLoc->getTimeSep() != sStrArray[i])
+ {
+ return nPos;
+ }
+ break;
+ }
+ break;
+ case SvNumFormatType::DATETIME:
+ switch (eNewType)
+ {
+ case SvNumFormatType::TIME:
+ case SvNumFormatType::DATE:
+ break;
+ case SvNumFormatType::FRACTION: // DD/MM
+ break;
+ default:
+ if (nCurrPos >= 0)
+ {
+ eScannedType = SvNumFormatType::UNDEFINED;
+ }
+ else if ( pFormatter->GetDateSep() != sStrArray[i] &&
+ pLoc->getTimeSep() != sStrArray[i] )
+ {
+ return nPos;
+ }
+ }
+ break;
+ case SvNumFormatType::PERCENT:
+ switch (eNewType)
+ {
+ case SvNumFormatType::NUMBER: // Only number to percent
+ break;
+ default:
+ return nPos;
+ }
+ break;
+ case SvNumFormatType::SCIENTIFIC:
+ switch (eNewType)
+ {
+ case SvNumFormatType::NUMBER: // Only number to E
+ break;
+ default:
+ return nPos;
+ }
+ break;
+ case SvNumFormatType::NUMBER:
+ switch (eNewType)
+ {
+ case SvNumFormatType::SCIENTIFIC:
+ case SvNumFormatType::PERCENT:
+ case SvNumFormatType::FRACTION:
+ case SvNumFormatType::CURRENCY:
+ eScannedType = eNewType;
+ break;
+ default:
+ if (nCurrPos >= 0)
+ {
+ eScannedType = SvNumFormatType::UNDEFINED;
+ }
+ else
+ {
+ return nPos;
+ }
+ }
+ break;
+ case SvNumFormatType::FRACTION:
+ switch (eNewType)
+ {
+ case SvNumFormatType::NUMBER: // Only number to fraction
+ break;
+ default:
+ return nPos;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ nPos = nPos + sStrArray[i].getLength(); // Position of correction
+ i++;
+ if ( bMatchBracket )
+ { // no type detection inside of matching brackets if [$...], [~...]
+ while ( bMatchBracket && i < nStringsCnt )
+ {
+ if ( nTypeArray[i] == NF_SYMBOLTYPE_DEL
+ && sStrArray[i][0] == ']' )
+ {
+ bMatchBracket = false;
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ }
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ if ( bMatchBracket )
+ {
+ return nPos; // missing closing bracket at end of code
+ }
+ }
+ SkipStrings(i, nPos);
+ }
+
+ if ((eScannedType == SvNumFormatType::NUMBER ||
+ eScannedType == SvNumFormatType::UNDEFINED) &&
+ nCurrPos >= 0 && !bHaveGeneral)
+ {
+ eScannedType = SvNumFormatType::CURRENCY; // old "automatic" currency
+ }
+ if (eScannedType == SvNumFormatType::UNDEFINED)
+ {
+ eScannedType = SvNumFormatType::DEFINED;
+ }
+ return 0; // All is fine
+}
+
+bool ImpSvNumberformatScan::InsertSymbol( sal_uInt16 & nPos, svt::NfSymbolType eType, const OUString& rStr )
+{
+ if (nStringsCnt >= NF_MAX_FORMAT_SYMBOLS || nPos > nStringsCnt)
+ {
+ return false;
+ }
+ if (nPos > 0 && nTypeArray[nPos-1] == NF_SYMBOLTYPE_EMPTY)
+ {
+ --nPos; // reuse position
+ }
+ else
+ {
+ if (nStringsCnt >= NF_MAX_FORMAT_SYMBOLS - 1)
+ {
+ return false;
+ }
+ ++nStringsCnt;
+ for (size_t i = nStringsCnt; i > nPos; --i)
+ {
+ nTypeArray[i] = nTypeArray[i-1];
+ sStrArray[i] = sStrArray[i-1];
+ }
+ }
+ ++nResultStringsCnt;
+ nTypeArray[nPos] = static_cast<short>(eType);
+ sStrArray[nPos] = rStr;
+ return true;
+}
+
+int ImpSvNumberformatScan::FinalScanGetCalendar( sal_Int32& nPos, sal_uInt16& i,
+ sal_uInt16& rResultStringsCnt )
+{
+ if ( i < nStringsCnt-1 &&
+ sStrArray[i][0] == '[' &&
+ nTypeArray[i+1] == NF_SYMBOLTYPE_STRING &&
+ sStrArray[i+1][0] == '~' )
+ {
+ // [~calendarID]
+ nPos = nPos + sStrArray[i].getLength(); // [
+ nTypeArray[i] = NF_SYMBOLTYPE_CALDEL;
+ nPos = nPos + sStrArray[++i].getLength(); // ~
+ sStrArray[i-1] += sStrArray[i]; // [~
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ rResultStringsCnt--;
+ if ( ++i >= nStringsCnt )
+ {
+ return -1; // error
+ }
+ nPos = nPos + sStrArray[i].getLength(); // calendarID
+ OUString& rStr = sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_CALENDAR; // convert
+ i++;
+ while ( i < nStringsCnt && sStrArray[i][0] != ']' )
+ {
+ nPos = nPos + sStrArray[i].getLength();
+ rStr += sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ rResultStringsCnt--;
+ i++;
+ }
+ if ( rStr.getLength() && i < nStringsCnt &&
+ sStrArray[i][0] == ']' )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_CALDEL;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ else
+ {
+ return -1; // error
+ }
+ return 1;
+ }
+ return 0;
+}
+
+bool ImpSvNumberformatScan::IsDateFragment( size_t nPos1, size_t nPos2 ) const
+{
+ return nPos2 - nPos1 == 2 && nTypeArray[nPos1+1] == NF_SYMBOLTYPE_DATESEP;
+}
+
+void ImpSvNumberformatScan::SwapArrayElements( size_t nPos1, size_t nPos2 )
+{
+ std::swap( nTypeArray[nPos1], nTypeArray[nPos2]);
+ std::swap( sStrArray[nPos1], sStrArray[nPos2]);
+}
+
+sal_Int32 ImpSvNumberformatScan::FinalScan( OUString& rString )
+{
+ const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData();
+
+ // save values for convert mode
+ OUString sOldDecSep = pFormatter->GetNumDecimalSep();
+ OUString sOldThousandSep = pFormatter->GetNumThousandSep();
+ OUString sOldDateSep = pFormatter->GetDateSep();
+ OUString sOldTimeSep = pLoc->getTimeSep();
+ OUString sOldTime100SecSep= pLoc->getTime100SecSep();
+ OUString sOldCurSymbol = GetCurSymbol();
+ OUString sOldCurString = GetCurString();
+ sal_Unicode cOldKeyH = sKeyword[NF_KEY_H][0];
+ sal_Unicode cOldKeyMI = sKeyword[NF_KEY_MI][0];
+ sal_Unicode cOldKeyS = sKeyword[NF_KEY_S][0];
+ DateOrder eOldDateOrder = pLoc->getDateOrder();
+ sal_uInt16 nDayPos, nMonthPos, nYearPos;
+ nDayPos = nMonthPos = nYearPos = SAL_MAX_UINT16;
+
+ // If the group separator is a No-Break Space (French) continue with a
+ // normal space instead so queries on space work correctly.
+ // The same for Narrow No-Break Space just in case some locale uses it.
+ // The format string is adjusted to allow both.
+ // For output of the format code string the LocaleData characters are used.
+ if ( (sOldThousandSep[0] == cNoBreakSpace || sOldThousandSep[0] == cNarrowNoBreakSpace) &&
+ sOldThousandSep.getLength() == 1 )
+ {
+ sOldThousandSep = " ";
+ }
+ bool bNewDateOrder = false;
+ // change locale data et al
+ if (bConvertMode)
+ {
+ pFormatter->ChangeIntl(eNewLnge);
+ //! pointer may have changed
+ pLoc = pFormatter->GetLocaleData();
+ //! init new keywords
+ InitKeywords();
+ // Adapt date order to target locale, but Excel does not handle date
+ // particle re-ordering for the target locale when loading documents,
+ // though it does exchange separators, tdf#113889
+ bNewDateOrder = (mbConvertDateOrder && eOldDateOrder != pLoc->getDateOrder());
+ }
+ const CharClass* pChrCls = pFormatter->GetCharClass();
+
+ sal_Int32 nPos = 0; // error correction position
+ sal_uInt16 i = 0; // symbol loop counter
+ sal_uInt16 nCounter = 0; // counts digits
+ nResultStringsCnt = nStringsCnt; // counts remaining symbols
+ bDecSep = false; // reset in case already used in TypeCheck
+ bool bThaiT = false; // Thai T NatNum modifier present
+ bool bTimePart = false;
+ bool bDenomin = false; // Set when reading end of denominator
+
+ switch (eScannedType)
+ {
+ case SvNumFormatType::TEXT:
+ case SvNumFormatType::DEFINED:
+ while (i < nStringsCnt)
+ {
+ switch (nTypeArray[i])
+ {
+ case NF_SYMBOLTYPE_BLANK:
+ case NF_SYMBOLTYPE_STAR:
+ break;
+ case NF_KEY_GENERAL : // #77026# "General" is the same as "@"
+ break;
+ default:
+ if ( nTypeArray[i] != NF_SYMBOLTYPE_DEL ||
+ sStrArray[i][0] != '@' )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ }
+ break;
+ }
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ } // of while
+ break;
+
+ case SvNumFormatType::NUMBER:
+ case SvNumFormatType::PERCENT:
+ case SvNumFormatType::CURRENCY:
+ case SvNumFormatType::SCIENTIFIC:
+ case SvNumFormatType::FRACTION:
+ while (i < nStringsCnt)
+ {
+ // TODO: rechecking eScannedType is unnecessary.
+ // This switch-case is for eScannedType == SvNumFormatType::FRACTION anyway
+ if (eScannedType == SvNumFormatType::FRACTION && // special case
+ nTypeArray[i] == NF_SYMBOLTYPE_DEL && // # ### #/#
+ StringEqualsChar( sOldThousandSep, ' ' ) && // e.g. France or Sweden
+ StringEqualsChar( sStrArray[i], ' ' ) &&
+ !bFrac &&
+ IsLastBlankBeforeFrac(i) )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING; // del->string
+ } // No thousands marker
+
+ if (nTypeArray[i] == NF_SYMBOLTYPE_BLANK ||
+ nTypeArray[i] == NF_SYMBOLTYPE_STAR ||
+ nTypeArray[i] == NF_KEY_CCC || // CCC
+ nTypeArray[i] == NF_KEY_GENERAL ) // Standard
+ {
+ if (nTypeArray[i] == NF_KEY_GENERAL)
+ {
+ nThousand = FLAG_STANDARD_IN_FORMAT;
+ if ( bConvertMode )
+ {
+ sStrArray[i] = sNameStandardFormat;
+ }
+ }
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ else if (nTypeArray[i] == NF_SYMBOLTYPE_STRING || // No Strings or
+ nTypeArray[i] > 0) // Keywords
+ {
+ if (eScannedType == SvNumFormatType::SCIENTIFIC &&
+ nTypeArray[i] == NF_KEY_E) // E+
+ {
+ if (bExp) // Double
+ {
+ return nPos;
+ }
+ bExp = true;
+ nExpPos = i;
+ if (bDecSep)
+ {
+ nCntPost = nCounter;
+ }
+ else
+ {
+ nCntPre = nCounter;
+ }
+ nCounter = 0;
+ nTypeArray[i] = NF_SYMBOLTYPE_EXP;
+ }
+ else if (eScannedType == SvNumFormatType::FRACTION &&
+ (sStrArray[i][0] == ' ' || ( nTypeArray[i] == NF_SYMBOLTYPE_STRING && (sStrArray[i][0] < '0' || sStrArray[i][0] > '9') ) ) )
+ {
+ if (!bBlank && !bFrac) // Not double or after a /
+ {
+ if (bDecSep && nCounter > 0) // Decimal places
+ {
+ return nPos; // Error
+ }
+ if (sStrArray[i][0] == ' ' || nCounter > 0 ) // treat string as integer/fraction delimiter only if there is integer
+ {
+ bBlank = true;
+ nBlankPos = i;
+ nCntPre = nCounter;
+ nCounter = 0;
+ nTypeArray[i] = NF_SYMBOLTYPE_FRACBLANK;
+ }
+ }
+ else if ( sStrArray[i][0] == ' ' )
+ nTypeArray[i] = NF_SYMBOLTYPE_FRACBLANK;
+ else if ( bFrac && ( nCounter > 0 ) )
+ bDenomin = true; // following elements are no more part of denominator
+ }
+ else if (nTypeArray[i] == NF_KEY_THAI_T)
+ {
+ bThaiT = true;
+ sStrArray[i] = sKeyword[nTypeArray[i]];
+ }
+ else if (sStrArray[i][0] >= '0' &&
+ sStrArray[i][0] <= '9' && !bDenomin) // denominator was not yet found
+ {
+ OUString sDiv;
+ sal_uInt16 j = i;
+ while(j < nStringsCnt && sStrArray[j][0] >= '0' && sStrArray[j][0] <= '9')
+ {
+ sDiv += sStrArray[j++];
+ }
+ assert(j > 0 && "if i is 0, first iteration through loop is guaranteed by surrounding if condition");
+ if (std::u16string_view(OUString::number(sDiv.toInt32())) == sDiv)
+ {
+ // Found a Divisor
+ while (i < j)
+ {
+ nTypeArray[i++] = NF_SYMBOLTYPE_FRAC_FDIV;
+ }
+ i = j - 1; // Stop the loop
+ if (nCntPost)
+ {
+ nCounter = nCntPost;
+ }
+ else if (nCntPre)
+ {
+ nCounter = nCntPre;
+ }
+ // don't artificially increment nCntPre for forced denominator
+ if ( ( eScannedType != SvNumFormatType::FRACTION ) && (!nCntPre) )
+ {
+ nCntPre++;
+ }
+ if ( bFrac )
+ bDenomin = true; // next content should be treated as outside denominator
+ }
+ }
+ else
+ {
+ if ( bFrac && ( nCounter > 0 ) )
+ bDenomin = true; // next content should be treated as outside denominator
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ }
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ else if (nTypeArray[i] == NF_SYMBOLTYPE_DEL)
+ {
+ sal_Unicode cHere = sStrArray[i][0];
+ sal_Unicode cSaved = cHere;
+ // Handle not pre-known separators in switch.
+ sal_Unicode cSimplified;
+ if (StringEqualsChar( pFormatter->GetNumThousandSep(), cHere))
+ {
+ cSimplified = ',';
+ }
+ else if (StringEqualsChar( pFormatter->GetNumDecimalSep(), cHere))
+ {
+ cSimplified = '.';
+ }
+ else
+ {
+ cSimplified = cHere;
+ }
+
+ OUString& rStr = sStrArray[i];
+
+ switch ( cSimplified )
+ {
+ case '#':
+ case '0':
+ case '?':
+ if (nThousand > 0) // #... #
+ {
+ return nPos; // Error
+ }
+ if ( !bDenomin )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_DIGIT;
+ nPos = nPos + rStr.getLength();
+ i++;
+ nCounter++;
+ while (i < nStringsCnt &&
+ (sStrArray[i][0] == '#' ||
+ sStrArray[i][0] == '0' ||
+ sStrArray[i][0] == '?'))
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_DIGIT;
+ nPos = nPos + sStrArray[i].getLength();
+ nCounter++;
+ i++;
+ }
+ }
+ else // after denominator, treat any character as text
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ }
+ break;
+ case '-':
+ if ( bDecSep && nDecPos+1 == i &&
+ nTypeArray[nDecPos] == NF_SYMBOLTYPE_DECSEP )
+ {
+ // "0.--"
+ nTypeArray[i] = NF_SYMBOLTYPE_DIGIT;
+ nPos = nPos + rStr.getLength();
+ i++;
+ nCounter++;
+ while (i < nStringsCnt &&
+ (sStrArray[i][0] == '-') )
+ {
+ // If more than two dashes are present in
+ // currency formats the last dash will be
+ // interpreted literally as a minus sign.
+ // Has to be this ugly. Period.
+ if ( eScannedType == SvNumFormatType::CURRENCY
+ && rStr.getLength() >= 2 &&
+ (i == nStringsCnt-1 ||
+ sStrArray[i+1][0] != '-') )
+ {
+ break;
+ }
+ rStr += sStrArray[i];
+ nPos = nPos + sStrArray[i].getLength();
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ nCounter++;
+ i++;
+ }
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ break;
+ case '.':
+ case ',':
+ case '\'':
+ case ' ':
+ if ( StringEqualsChar( sOldThousandSep, cSaved ) )
+ {
+ // previous char with skip empty
+ sal_Unicode cPre = PreviousChar(i);
+ sal_Unicode cNext;
+ if (bExp || bBlank || bFrac)
+ {
+ // after E, / or ' '
+ if ( !StringEqualsChar( sOldThousandSep, ' ' ) )
+ {
+ nPos = nPos + sStrArray[i].getLength();
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ i++; // eat it
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ if ( bFrac && (nCounter > 0) )
+ bDenomin = true; // end of denominator
+ }
+ }
+ else if (i > 0 && i < nStringsCnt-1 &&
+ (cPre == '#' || cPre == '0' || cPre == '?') &&
+ ((cNext = NextChar(i)) == '#' || cNext == '0' || cNext == '?')) // #,#
+ {
+ nPos = nPos + sStrArray[i].getLength();
+ if (!bThousand) // only once
+ {
+ bThousand = true;
+ }
+ // Eat it, will be reinserted at proper grouping positions further down.
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ i++;
+ }
+ else if (i > 0 && (cPre == '#' || cPre == '0' || cPre == '?')
+ && PreviousType(i) == NF_SYMBOLTYPE_DIGIT
+ && nThousand < FLAG_STANDARD_IN_FORMAT )
+ { // #,,,,
+ if ( StringEqualsChar( sOldThousandSep, ' ' ) )
+ {
+ // strange, those French...
+ bool bFirst = true;
+ // set a hard No-Break Space or ConvertMode
+ const OUString& rSepF = pFormatter->GetNumThousandSep();
+ while ( i < nStringsCnt &&
+ sStrArray[i] == sOldThousandSep &&
+ StringEqualsChar( sOldThousandSep, NextChar(i) ) )
+ { // last was a space or another space
+ // is following => separator
+ nPos = nPos + sStrArray[i].getLength();
+ if ( bFirst )
+ {
+ bFirst = false;
+ rStr = rSepF;
+ nTypeArray[i] = NF_SYMBOLTYPE_THSEP;
+ }
+ else
+ {
+ rStr += rSepF;
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ nThousand++;
+ i++;
+ }
+ if ( i < nStringsCnt-1 &&
+ sStrArray[i] == sOldThousandSep )
+ {
+ // something following last space
+ // => space if currency contained,
+ // else separator
+ nPos = nPos + sStrArray[i].getLength();
+ if ( (nPos <= nCurrPos &&
+ nCurrPos < nPos + sStrArray[i+1].getLength()) ||
+ nTypeArray[i+1] == NF_KEY_CCC ||
+ (i < nStringsCnt-2 &&
+ sStrArray[i+1][0] == '[' &&
+ sStrArray[i+2][0] == '$') )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ }
+ else
+ {
+ if ( bFirst )
+ {
+ rStr = rSepF;
+ nTypeArray[i] = NF_SYMBOLTYPE_THSEP;
+ }
+ else
+ {
+ rStr += rSepF;
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ nThousand++;
+ }
+ i++;
+ }
+ }
+ else
+ {
+ do
+ {
+ nThousand++;
+ nTypeArray[i] = NF_SYMBOLTYPE_THSEP;
+ nPos = nPos + sStrArray[i].getLength();
+ sStrArray[i] = pFormatter->GetNumThousandSep();
+ i++;
+ }
+ while (i < nStringsCnt && sStrArray[i] == sOldThousandSep);
+ }
+ }
+ else // any grsep
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + rStr.getLength();
+ i++;
+ while ( i < nStringsCnt && sStrArray[i] == sOldThousandSep )
+ {
+ rStr += sStrArray[i];
+ nPos = nPos + sStrArray[i].getLength();
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ i++;
+ }
+ }
+ }
+ else if ( StringEqualsChar( sOldDecSep, cSaved ) )
+ {
+ if (bBlank || bFrac) // . behind / or ' '
+ {
+ return nPos; // error
+ }
+ else if (bExp) // behind E
+ {
+ nPos = nPos + sStrArray[i].getLength();
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ i++; // eat it
+ }
+ else if (bDecSep) // any .
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + rStr.getLength();
+ i++;
+ while ( i < nStringsCnt && sStrArray[i] == sOldDecSep )
+ {
+ rStr += sStrArray[i];
+ nPos = nPos + sStrArray[i].getLength();
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ i++;
+ }
+ }
+ else
+ {
+ nPos = nPos + sStrArray[i].getLength();
+ nTypeArray[i] = NF_SYMBOLTYPE_DECSEP;
+ sStrArray[i] = pFormatter->GetNumDecimalSep();
+ bDecSep = true;
+ nDecPos = i;
+ nCntPre = nCounter;
+ nCounter = 0;
+
+ i++;
+ }
+ } // of else = DecSep
+ else // . without meaning
+ {
+ if (cSaved == ' ' &&
+ eScannedType == SvNumFormatType::FRACTION &&
+ StringEqualsChar( sStrArray[i], ' ' ) )
+ {
+ if (!bBlank && !bFrac) // no dups
+ { // or behind /
+ if (bDecSep && nCounter > 0) // dec.
+ {
+ return nPos; // error
+ }
+ bBlank = true;
+ nBlankPos = i;
+ nCntPre = nCounter;
+ nCounter = 0;
+ }
+ if ( bFrac && (nCounter > 0) )
+ bDenomin = true; // next content is not part of denominator
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ if ( bFrac && (nCounter > 0) )
+ bDenomin = true; // next content is not part of denominator
+ nPos = nPos + rStr.getLength();
+ i++;
+ while (i < nStringsCnt && StringEqualsChar( sStrArray[i], cSaved ) )
+ {
+ rStr += sStrArray[i];
+ nPos = nPos + sStrArray[i].getLength();
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ i++;
+ }
+ }
+ }
+ break;
+ case '/':
+ if (eScannedType == SvNumFormatType::FRACTION)
+ {
+ if ( i == 0 ||
+ (nTypeArray[i-1] != NF_SYMBOLTYPE_DIGIT &&
+ nTypeArray[i-1] != NF_SYMBOLTYPE_EMPTY) )
+ {
+ return nPos ? nPos : 1; // /? not allowed
+ }
+ else if (!bFrac || (bDecSep && nCounter > 0))
+ {
+ bFrac = true;
+ nCntPost = nCounter;
+ nCounter = 0;
+ nTypeArray[i] = NF_SYMBOLTYPE_FRAC;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ else // / double or in , in the denominator
+ {
+ return nPos; // Error
+ }
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ break;
+ case '[' :
+ if ( eScannedType == SvNumFormatType::CURRENCY &&
+ i < nStringsCnt-1 &&
+ nTypeArray[i+1] == NF_SYMBOLTYPE_STRING &&
+ sStrArray[i+1][0] == '$' )
+ {
+ // [$DM-xxx]
+ nPos = nPos + sStrArray[i].getLength(); // [
+ nTypeArray[i] = NF_SYMBOLTYPE_CURRDEL;
+ nPos = nPos + sStrArray[++i].getLength(); // $
+ sStrArray[i-1] += sStrArray[i]; // [$
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ if ( ++i >= nStringsCnt )
+ {
+ return nPos; // Error
+ }
+ nPos = nPos + sStrArray[i].getLength(); // DM
+ OUString* pStr = &sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_CURRENCY; // convert
+ bool bHadDash = false;
+ i++;
+ while ( i < nStringsCnt && sStrArray[i][0] != ']' )
+ {
+ nPos = nPos + sStrArray[i].getLength();
+ if ( bHadDash )
+ {
+ *pStr += sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ else
+ {
+ if ( sStrArray[i][0] == '-' )
+ {
+ bHadDash = true;
+ pStr = &sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_CURREXT;
+ }
+ else
+ {
+ *pStr += sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ }
+ i++;
+ }
+ if ( rStr.getLength() && i < nStringsCnt && sStrArray[i][0] == ']' )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_CURRDEL;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ else
+ {
+ return nPos; // Error
+ }
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ break;
+ default: // Other Dels
+ if (eScannedType == SvNumFormatType::PERCENT && cHere == '%')
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_PERCENT;
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ }
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ } // of switch (Del)
+ } // of else Del
+ else
+ {
+ SAL_WARN( "svl.numbers", "unknown NF_SYMBOLTYPE_..." );
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ } // of while
+ if (eScannedType == SvNumFormatType::FRACTION)
+ {
+ if (bFrac)
+ {
+ nCntExp = nCounter;
+ }
+ else if (bBlank)
+ {
+ nCntPost = nCounter;
+ }
+ else
+ {
+ nCntPre = nCounter;
+ }
+ }
+ else
+ {
+ if (bExp)
+ {
+ nCntExp = nCounter;
+ }
+ else if (bDecSep)
+ {
+ nCntPost = nCounter;
+ }
+ else
+ {
+ nCntPre = nCounter;
+ }
+ }
+ if (bThousand) // Expansion of grouping separators
+ {
+ sal_uInt16 nMaxPos;
+ if (bFrac)
+ {
+ if (bBlank)
+ {
+ nMaxPos = nBlankPos;
+ }
+ else
+ {
+ nMaxPos = 0; // no grouping
+ }
+ }
+ else if (bDecSep) // decimal separator present
+ {
+ nMaxPos = nDecPos;
+ }
+ else if (bExp) // 'E' exponent present
+ {
+ nMaxPos = nExpPos;
+ }
+ else // up to end
+ {
+ nMaxPos = i;
+ }
+ // Insert separators at proper positions.
+ sal_Int32 nCount = 0;
+ utl::DigitGroupingIterator aGrouping( pLoc->getDigitGrouping());
+ size_t nFirstDigitSymbol = nMaxPos;
+ size_t nFirstGroupingSymbol = nMaxPos;
+ i = nMaxPos;
+ while (i-- > 0)
+ {
+ if (nTypeArray[i] == NF_SYMBOLTYPE_DIGIT)
+ {
+ nFirstDigitSymbol = i;
+ nCount = nCount + sStrArray[i].getLength(); // MSC converts += to int and then warns, so ...
+ // Insert separator only if not leftmost symbol.
+ if (i > 0 && nCount >= aGrouping.getPos())
+ {
+ DBG_ASSERT( sStrArray[i].getLength() == 1,
+ "ImpSvNumberformatScan::FinalScan: combined digits in group separator insertion");
+ if (!InsertSymbol( i, NF_SYMBOLTYPE_THSEP, pFormatter->GetNumThousandSep()))
+ {
+ // nPos isn't correct here, but signals error
+ return nPos;
+ }
+ // i may have been decremented by 1
+ nFirstDigitSymbol = i + 1;
+ nFirstGroupingSymbol = i;
+ aGrouping.advance();
+ }
+ }
+ }
+ // Generated something like "string",000; remove separator again.
+ if (nFirstGroupingSymbol < nFirstDigitSymbol)
+ {
+ nTypeArray[nFirstGroupingSymbol] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ }
+ // Combine digits into groups to save memory (Info will be copied
+ // later, taking only non-empty symbols).
+ for (i = 0; i < nStringsCnt; ++i)
+ {
+ if (nTypeArray[i] == NF_SYMBOLTYPE_DIGIT)
+ {
+ OUString& rStr = sStrArray[i];
+ while (++i < nStringsCnt && nTypeArray[i] == NF_SYMBOLTYPE_DIGIT)
+ {
+ rStr += sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ }
+ }
+ break; // of SvNumFormatType::NUMBER
+ case SvNumFormatType::DATE:
+ while (i < nStringsCnt)
+ {
+ switch (nTypeArray[i])
+ {
+ case NF_SYMBOLTYPE_BLANK:
+ case NF_SYMBOLTYPE_STAR:
+ case NF_SYMBOLTYPE_STRING:
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ case NF_SYMBOLTYPE_DEL:
+ int nCalRet;
+ if (sStrArray[i] == sOldDateSep)
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_DATESEP;
+ nPos = nPos + sStrArray[i].getLength();
+ if (bConvertMode)
+ {
+ sStrArray[i] = pFormatter->GetDateSep();
+ }
+ i++;
+ }
+ else if ( (nCalRet = FinalScanGetCalendar( nPos, i, nResultStringsCnt )) != 0 )
+ {
+ if ( nCalRet < 0 )
+ {
+ return nPos; // error
+ }
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ break;
+ case NF_KEY_THAI_T :
+ bThaiT = true;
+ [[fallthrough]];
+ case NF_KEY_M: // M
+ case NF_KEY_MM: // MM
+ case NF_KEY_MMM: // MMM
+ case NF_KEY_MMMM: // MMMM
+ case NF_KEY_MMMMM: // MMMMM
+ case NF_KEY_Q: // Q
+ case NF_KEY_QQ: // QQ
+ case NF_KEY_D: // D
+ case NF_KEY_DD: // DD
+ case NF_KEY_DDD: // DDD
+ case NF_KEY_DDDD: // DDDD
+ case NF_KEY_YY: // YY
+ case NF_KEY_YYYY: // YYYY
+ case NF_KEY_NN: // NN
+ case NF_KEY_NNN: // NNN
+ case NF_KEY_NNNN: // NNNN
+ case NF_KEY_WW : // WW
+ case NF_KEY_AAA : // AAA
+ case NF_KEY_AAAA : // AAAA
+ case NF_KEY_EC : // E
+ case NF_KEY_EEC : // EE
+ case NF_KEY_G : // G
+ case NF_KEY_GG : // GG
+ case NF_KEY_GGG : // GGG
+ case NF_KEY_R : // R
+ case NF_KEY_RR : // RR
+ sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
+ nPos = nPos + sStrArray[i].getLength();
+ if (bNewDateOrder)
+ {
+ // For simple numeric date formats record date order and
+ // later rearrange.
+ switch (nTypeArray[i])
+ {
+ case NF_KEY_M:
+ case NF_KEY_MM:
+ if (nMonthPos == SAL_MAX_UINT16)
+ nMonthPos = i;
+ else
+ bNewDateOrder = false;
+ break;
+ case NF_KEY_D:
+ case NF_KEY_DD:
+ if (nDayPos == SAL_MAX_UINT16)
+ nDayPos = i;
+ else
+ bNewDateOrder = false;
+ break;
+ case NF_KEY_YY:
+ case NF_KEY_YYYY:
+ if (nYearPos == SAL_MAX_UINT16)
+ nYearPos = i;
+ else
+ bNewDateOrder = false;
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ i++;
+ break;
+ default: // Other keywords
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ }
+ } // of while
+ break; // of SvNumFormatType::DATE
+ case SvNumFormatType::TIME:
+ while (i < nStringsCnt)
+ {
+ sal_Unicode cChar;
+
+ switch (nTypeArray[i])
+ {
+ case NF_SYMBOLTYPE_BLANK:
+ case NF_SYMBOLTYPE_STAR:
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ case NF_SYMBOLTYPE_DEL:
+ switch( sStrArray[i][0] )
+ {
+ case '0':
+ if ( Is100SecZero( i, bDecSep ) )
+ {
+ bDecSep = true;
+ nTypeArray[i] = NF_SYMBOLTYPE_DIGIT;
+ OUString& rStr = sStrArray[i];
+ nCounter++;
+ i++;
+ while (i < nStringsCnt &&
+ sStrArray[i][0] == '0')
+ {
+ rStr += sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ nCounter++;
+ i++;
+ }
+ nPos += rStr.getLength();
+ }
+ else
+ {
+ return nPos;
+ }
+ break;
+ case '#':
+ case '?':
+ return nPos;
+ case '[':
+ if (bThousand) // Double
+ {
+ return nPos;
+ }
+ bThousand = true; // Empty for Time
+ cChar = pChrCls->uppercase(OUString(NextChar(i)))[0];
+ if ( cChar == cOldKeyH )
+ {
+ nThousand = 1; // H
+ }
+ else if ( cChar == cOldKeyMI )
+ {
+ nThousand = 2; // M
+ }
+ else if ( cChar == cOldKeyS )
+ {
+ nThousand = 3; // S
+ }
+ else
+ {
+ return nPos;
+ }
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ case ']':
+ if (!bThousand) // No preceding [
+ {
+ return nPos;
+ }
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ default:
+ nPos = nPos + sStrArray[i].getLength();
+ if ( sStrArray[i] == sOldTimeSep )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_TIMESEP;
+ if ( bConvertMode )
+ {
+ sStrArray[i] = pLoc->getTimeSep();
+ }
+ }
+ else if ( sStrArray[i] == sOldTime100SecSep )
+ {
+ bDecSep = true;
+ nTypeArray[i] = NF_SYMBOLTYPE_TIME100SECSEP;
+ if ( bConvertMode )
+ {
+ sStrArray[i] = pLoc->getTime100SecSep();
+ }
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ }
+ i++;
+ break;
+ }
+ break;
+ case NF_SYMBOLTYPE_STRING:
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ case NF_KEY_AMPM: // AM/PM
+ case NF_KEY_AP: // A/P
+ bExp = true; // Abuse for A/P
+ sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ case NF_KEY_THAI_T :
+ bThaiT = true;
+ [[fallthrough]];
+ case NF_KEY_MI: // M
+ case NF_KEY_MMI: // MM
+ case NF_KEY_H: // H
+ case NF_KEY_HH: // HH
+ case NF_KEY_S: // S
+ case NF_KEY_SS: // SS
+ sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ default: // Other keywords
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ }
+ } // of while
+ nCntPost = nCounter; // Zero counter
+ if (bExp)
+ {
+ nCntExp = 1; // Remembers AM/PM
+ }
+ break; // of SvNumFormatType::TIME
+ case SvNumFormatType::DATETIME:
+ while (i < nStringsCnt)
+ {
+ int nCalRet;
+ switch (nTypeArray[i])
+ {
+ case NF_SYMBOLTYPE_BLANK:
+ case NF_SYMBOLTYPE_STAR:
+ case NF_SYMBOLTYPE_STRING:
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ case NF_SYMBOLTYPE_DEL:
+ if ( (nCalRet = FinalScanGetCalendar( nPos, i, nResultStringsCnt )) != 0 )
+ {
+ if ( nCalRet < 0 )
+ {
+ return nPos; // Error
+ }
+ }
+ else
+ {
+ switch( sStrArray[i][0] )
+ {
+ case '0':
+ if (bTimePart && Is100SecZero(i, bDecSep) && nCounter < MaxCntPost)
+ {
+ bDecSep = true;
+ nTypeArray[i] = NF_SYMBOLTYPE_DIGIT;
+ OUString& rStr = sStrArray[i];
+ nCounter++;
+ i++;
+ while (i < nStringsCnt &&
+ sStrArray[i][0] == '0' && nCounter < MaxCntPost)
+ {
+ rStr += sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ nCounter++;
+ i++;
+ }
+ nPos += rStr.getLength();
+ }
+ else
+ {
+ return nPos;
+ }
+ break;
+ case '#':
+ case '?':
+ return nPos;
+ default:
+ nPos = nPos + sStrArray[i].getLength();
+ if (bTimePart)
+ {
+ if ( sStrArray[i] == sOldTimeSep )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_TIMESEP;
+ if ( bConvertMode )
+ {
+ sStrArray[i] = pLoc->getTimeSep();
+ }
+ }
+ else if ( sStrArray[i] == sOldTime100SecSep )
+ {
+ bDecSep = true;
+ nTypeArray[i] = NF_SYMBOLTYPE_TIME100SECSEP;
+ if ( bConvertMode )
+ {
+ sStrArray[i] = pLoc->getTime100SecSep();
+ }
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ }
+ }
+ else
+ {
+ if ( sStrArray[i] == sOldDateSep )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_DATESEP;
+ if (bConvertMode)
+ sStrArray[i] = pFormatter->GetDateSep();
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ }
+ }
+ i++;
+ break;
+ }
+ }
+ break;
+ case NF_KEY_AMPM: // AM/PM
+ case NF_KEY_AP: // A/P
+ bTimePart = true;
+ bExp = true; // Abuse for A/P
+ sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ case NF_KEY_MI: // M
+ case NF_KEY_MMI: // MM
+ case NF_KEY_H: // H
+ case NF_KEY_HH: // HH
+ case NF_KEY_S: // S
+ case NF_KEY_SS: // SS
+ bTimePart = true;
+ sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ case NF_KEY_M: // M
+ case NF_KEY_MM: // MM
+ case NF_KEY_MMM: // MMM
+ case NF_KEY_MMMM: // MMMM
+ case NF_KEY_MMMMM: // MMMMM
+ case NF_KEY_Q: // Q
+ case NF_KEY_QQ: // QQ
+ case NF_KEY_D: // D
+ case NF_KEY_DD: // DD
+ case NF_KEY_DDD: // DDD
+ case NF_KEY_DDDD: // DDDD
+ case NF_KEY_YY: // YY
+ case NF_KEY_YYYY: // YYYY
+ case NF_KEY_NN: // NN
+ case NF_KEY_NNN: // NNN
+ case NF_KEY_NNNN: // NNNN
+ case NF_KEY_WW : // WW
+ case NF_KEY_AAA : // AAA
+ case NF_KEY_AAAA : // AAAA
+ case NF_KEY_EC : // E
+ case NF_KEY_EEC : // EE
+ case NF_KEY_G : // G
+ case NF_KEY_GG : // GG
+ case NF_KEY_GGG : // GGG
+ case NF_KEY_R : // R
+ case NF_KEY_RR : // RR
+ bTimePart = false;
+ sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
+ nPos = nPos + sStrArray[i].getLength();
+ if (bNewDateOrder)
+ {
+ // For simple numeric date formats record date order and
+ // later rearrange.
+ switch (nTypeArray[i])
+ {
+ case NF_KEY_M:
+ case NF_KEY_MM:
+ if (nMonthPos == SAL_MAX_UINT16)
+ nMonthPos = i;
+ else
+ bNewDateOrder = false;
+ break;
+ case NF_KEY_D:
+ case NF_KEY_DD:
+ if (nDayPos == SAL_MAX_UINT16)
+ nDayPos = i;
+ else
+ bNewDateOrder = false;
+ break;
+ case NF_KEY_YY:
+ case NF_KEY_YYYY:
+ if (nYearPos == SAL_MAX_UINT16)
+ nYearPos = i;
+ else
+ bNewDateOrder = false;
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ i++;
+ break;
+ case NF_KEY_THAI_T :
+ bThaiT = true;
+ sStrArray[i] = sKeyword[nTypeArray[i]];
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ default: // Other keywords
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ }
+ } // of while
+ nCntPost = nCounter; // decimals (100th seconds)
+ if (bExp)
+ {
+ nCntExp = 1; // Remembers AM/PM
+ }
+ break; // of SvNumFormatType::DATETIME
+ default:
+ break;
+ }
+ if (eScannedType == SvNumFormatType::SCIENTIFIC &&
+ (nCntPre + nCntPost == 0 || nCntExp == 0))
+ {
+ return nPos;
+ }
+ else if (eScannedType == SvNumFormatType::FRACTION && (nCntExp > 8 || nCntExp == 0))
+ {
+ return nPos;
+ }
+ if (bThaiT && !GetNatNumModifier())
+ {
+ SetNatNumModifier(1);
+ }
+ if ( bConvertMode )
+ {
+ if (bNewDateOrder && sOldDateSep == "-")
+ {
+ // Keep ISO formats Y-M-D, Y-M and M-D
+ if (IsDateFragment( nYearPos, nMonthPos))
+ {
+ nTypeArray[nYearPos+1] = NF_SYMBOLTYPE_STRING;
+ sStrArray[nYearPos+1] = sOldDateSep;
+ bNewDateOrder = false;
+ }
+ if (IsDateFragment( nMonthPos, nDayPos))
+ {
+ nTypeArray[nMonthPos+1] = NF_SYMBOLTYPE_STRING;
+ sStrArray[nMonthPos+1] = sOldDateSep;
+ bNewDateOrder = false;
+ }
+ }
+ if (bNewDateOrder)
+ {
+ // Rearrange date order to the target locale if the original order
+ // includes date separators and is adjacent.
+ /* TODO: for incomplete dates trailing separators need to be
+ * handled according to the locale's usage, e.g. en-US M/D should
+ * be converted to de-DE D.M. and vice versa. As is, it's
+ * M/D -> D.M and D.M. -> M/D/ where specifically the latter looks
+ * odd. Check accepted date patterns and append/remove? */
+ switch (eOldDateOrder)
+ {
+ case DateOrder::DMY:
+ switch (pLoc->getDateOrder())
+ {
+ case DateOrder::MDY:
+ // Convert only if the actual format is not of YDM
+ // order (which would be a completely unusual order
+ // anyway, but..), e.g. YYYY.DD.MM not to
+ // YYYY/MM/DD
+ if (IsDateFragment( nDayPos, nMonthPos) && !IsDateFragment( nYearPos, nDayPos))
+ SwapArrayElements( nDayPos, nMonthPos);
+ break;
+ case DateOrder::YMD:
+ if (nYearPos != SAL_MAX_UINT16)
+ {
+ if (IsDateFragment( nDayPos, nMonthPos) && IsDateFragment( nMonthPos, nYearPos))
+ SwapArrayElements( nDayPos, nYearPos);
+ }
+ else
+ {
+ if (IsDateFragment( nDayPos, nMonthPos))
+ SwapArrayElements( nDayPos, nMonthPos);
+ }
+ break;
+ default:
+ ; // nothing
+ }
+ break;
+ case DateOrder::MDY:
+ switch (pLoc->getDateOrder())
+ {
+ case DateOrder::DMY:
+ // Convert only if the actual format is not of YMD
+ // order, e.g. YYYY/MM/DD not to YYYY.DD.MM
+ /* TODO: convert such to DD.MM.YYYY instead? */
+ if (IsDateFragment( nMonthPos, nDayPos) && !IsDateFragment( nYearPos, nMonthPos))
+ SwapArrayElements( nMonthPos, nDayPos);
+ break;
+ case DateOrder::YMD:
+ if (nYearPos != SAL_MAX_UINT16)
+ {
+ if (IsDateFragment( nMonthPos, nDayPos) && IsDateFragment( nDayPos, nYearPos))
+ {
+ SwapArrayElements( nYearPos, nMonthPos); // YDM
+ SwapArrayElements( nYearPos, nDayPos); // YMD
+ }
+ }
+ break;
+ default:
+ ; // nothing
+ }
+ break;
+ case DateOrder::YMD:
+ switch (pLoc->getDateOrder())
+ {
+ case DateOrder::DMY:
+ if (nYearPos != SAL_MAX_UINT16)
+ {
+ if (IsDateFragment( nYearPos, nMonthPos) && IsDateFragment( nMonthPos, nDayPos))
+ SwapArrayElements( nYearPos, nDayPos);
+ }
+ else
+ {
+ if (IsDateFragment( nMonthPos, nDayPos))
+ SwapArrayElements( nMonthPos, nDayPos);
+ }
+ break;
+ case DateOrder::MDY:
+ if (nYearPos != SAL_MAX_UINT16)
+ {
+ if (IsDateFragment( nYearPos, nMonthPos) && IsDateFragment( nMonthPos, nDayPos))
+ {
+ SwapArrayElements( nYearPos, nDayPos); // DMY
+ SwapArrayElements( nYearPos, nMonthPos); // MDY
+ }
+ }
+ break;
+ default:
+ ; // nothing
+ }
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ // strings containing keywords of the target locale must be quoted, so
+ // the user sees the difference and is able to edit the format string
+ for ( i=0; i < nStringsCnt; i++ )
+ {
+ if ( nTypeArray[i] == NF_SYMBOLTYPE_STRING &&
+ sStrArray[i][0] != '\"' )
+ {
+ if ( bConvertSystemToSystem && eScannedType == SvNumFormatType::CURRENCY )
+ {
+ // don't stringize automatic currency, will be converted
+ if ( sStrArray[i] == sOldCurSymbol )
+ {
+ continue; // for
+ }
+ // DM might be split into D and M
+ if ( sStrArray[i].getLength() < sOldCurSymbol.getLength() &&
+ pChrCls->uppercase( sStrArray[i], 0, 1 )[0] ==
+ sOldCurString[0] )
+ {
+ OUString aTmp( sStrArray[i] );
+ sal_uInt16 j = i + 1;
+ while ( aTmp.getLength() < sOldCurSymbol.getLength() &&
+ j < nStringsCnt &&
+ nTypeArray[j] == NF_SYMBOLTYPE_STRING )
+ {
+ aTmp += sStrArray[j++];
+ }
+ if ( pChrCls->uppercase( aTmp ) == sOldCurString )
+ {
+ sStrArray[i++] = aTmp;
+ for ( ; i<j; i++ )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ i = j - 1;
+ continue; // for
+ }
+ }
+ }
+ OUString& rStr = sStrArray[i];
+ sal_Int32 nLen = rStr.getLength();
+ for ( sal_Int32 j = 0; j < nLen; j++ )
+ {
+ bool bFoundEnglish = false;
+ if ( (j == 0 || rStr[j - 1] != '\\') && GetKeyWord( rStr, j, bFoundEnglish) )
+ {
+ rStr = "\"" + rStr + "\"";
+ break; // for
+ }
+ }
+ }
+ }
+ }
+ // Concatenate strings, remove quotes for output, and rebuild the format string
+ rString.clear();
+ i = 0;
+ while (i < nStringsCnt)
+ {
+ sal_Int32 nStringPos;
+ sal_Int32 nArrPos = 0;
+ sal_uInt16 iPos = i;
+ switch ( nTypeArray[i] )
+ {
+ case NF_SYMBOLTYPE_STRING :
+ case NF_SYMBOLTYPE_FRACBLANK :
+ nStringPos = rString.getLength();
+ do
+ {
+ if (sStrArray[i].getLength() == 2 &&
+ sStrArray[i][0] == '\\')
+ {
+ // Unescape some simple forms of symbols even in the UI
+ // visible string to prevent duplicates that differ
+ // only in notation, originating from import.
+ // e.g. YYYY-MM-DD and YYYY\-MM\-DD are identical,
+ // but 0\ 000 0 and 0 000 0 in a French locale are not.
+
+ sal_Unicode c = sStrArray[i][1];
+
+ switch (c)
+ {
+ case '+':
+ case '-':
+ rString += OUStringChar(c);
+ break;
+ case ' ':
+ case '.':
+ case '/':
+ if (!(eScannedType & SvNumFormatType::DATE) &&
+ (StringEqualsChar( pFormatter->GetNumThousandSep(), c) ||
+ StringEqualsChar( pFormatter->GetNumDecimalSep(), c) ||
+ (c == ' ' &&
+ (StringEqualsChar( pFormatter->GetNumThousandSep(), cNoBreakSpace) ||
+ StringEqualsChar( pFormatter->GetNumThousandSep(), cNarrowNoBreakSpace)))))
+ {
+ rString += sStrArray[i];
+ }
+ else if ((eScannedType & SvNumFormatType::DATE) &&
+ StringEqualsChar( pFormatter->GetDateSep(), c))
+ {
+ rString += sStrArray[i];
+ }
+ else if ((eScannedType & SvNumFormatType::TIME) &&
+ (StringEqualsChar( pLoc->getTimeSep(), c) ||
+ StringEqualsChar( pLoc->getTime100SecSep(), c)))
+ {
+ rString += sStrArray[i];
+ }
+ else if (eScannedType & SvNumFormatType::FRACTION)
+ {
+ rString += sStrArray[i];
+ }
+ else
+ {
+ rString += OUStringChar(c);
+ }
+ break;
+ default:
+ rString += sStrArray[i];
+ }
+ }
+ else
+ {
+ rString += sStrArray[i];
+ }
+ if ( RemoveQuotes( sStrArray[i] ) > 0 )
+ {
+ // update currency up to quoted string
+ if ( eScannedType == SvNumFormatType::CURRENCY )
+ {
+ // dM -> DM or DM -> $ in old automatic
+ // currency formats, oh my ..., why did we ever introduce them?
+ OUString aTmp( pChrCls->uppercase( sStrArray[iPos], nArrPos,
+ sStrArray[iPos].getLength()-nArrPos ) );
+ sal_Int32 nCPos = aTmp.indexOf( sOldCurString );
+ if ( nCPos >= 0 )
+ {
+ const OUString& rCur = bConvertMode && bConvertSystemToSystem ?
+ GetCurSymbol() : sOldCurSymbol;
+ sStrArray[iPos] = sStrArray[iPos].replaceAt( nArrPos + nCPos,
+ sOldCurString.getLength(),
+ rCur );
+ rString = rString.replaceAt( nStringPos + nCPos,
+ sOldCurString.getLength(),
+ rCur );
+ }
+ nStringPos = rString.getLength();
+ if ( iPos == i )
+ {
+ nArrPos = sStrArray[iPos].getLength();
+ }
+ else
+ {
+ nArrPos = sStrArray[iPos].getLength() + sStrArray[i].getLength();
+ }
+ }
+ }
+ if ( iPos != i )
+ {
+ sStrArray[iPos] += sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ i++;
+ }
+ while ( i < nStringsCnt && nTypeArray[i] == NF_SYMBOLTYPE_STRING );
+
+ if ( i < nStringsCnt )
+ {
+ i--; // enter switch on next symbol again
+ }
+ if ( eScannedType == SvNumFormatType::CURRENCY && nStringPos < rString.getLength() )
+ {
+ // same as above, since last RemoveQuotes
+ OUString aTmp( pChrCls->uppercase( sStrArray[iPos], nArrPos,
+ sStrArray[iPos].getLength()-nArrPos ) );
+ sal_Int32 nCPos = aTmp.indexOf( sOldCurString );
+ if ( nCPos >= 0 )
+ {
+ const OUString& rCur = bConvertMode && bConvertSystemToSystem ?
+ GetCurSymbol() : sOldCurSymbol;
+ sStrArray[iPos] = sStrArray[iPos].replaceAt( nArrPos + nCPos,
+ sOldCurString.getLength(),
+ rCur );
+ rString = rString.replaceAt( nStringPos + nCPos,
+ sOldCurString.getLength(), rCur );
+ }
+ }
+ break;
+ case NF_SYMBOLTYPE_CURRENCY :
+ rString += sStrArray[i];
+ RemoveQuotes( sStrArray[i] );
+ break;
+ case NF_KEY_THAI_T:
+ if (bThaiT && GetNatNumModifier() == 1)
+ {
+ // Remove T from format code, will be replaced with a [NatNum1] prefix.
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ else
+ {
+ rString += sStrArray[i];
+ }
+ break;
+ case NF_SYMBOLTYPE_EMPTY :
+ // nothing
+ break;
+ default:
+ rString += sStrArray[i];
+ }
+ i++;
+ }
+ return 0;
+}
+
+sal_Int32 ImpSvNumberformatScan::RemoveQuotes( OUString& rStr )
+{
+ if ( rStr.getLength() > 1 )
+ {
+ sal_Unicode c = rStr[0];
+ sal_Int32 n = rStr.getLength() - 1;
+ if ( c == '"' && rStr[n] == '"' )
+ {
+ rStr = rStr.copy( 1, n-1);
+ return 2;
+ }
+ else if ( c == '\\' )
+ {
+ rStr = rStr.copy(1);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+sal_Int32 ImpSvNumberformatScan::ScanFormat( OUString& rString )
+{
+ sal_Int32 res = Symbol_Division(rString); // Lexical analysis
+ if (!res)
+ {
+ res = ScanType(); // Recognizing the Format type
+ }
+ if (!res)
+ {
+ res = FinalScan( rString ); // Type dependent final analysis
+ }
+ return res; // res = control position; res = 0 => Format ok
+}
+
+void ImpSvNumberformatScan::CopyInfo(ImpSvNumberformatInfo* pInfo, sal_uInt16 nCnt)
+{
+ size_t i,j;
+ j = 0;
+ i = 0;
+ while (i < nCnt && j < NF_MAX_FORMAT_SYMBOLS)
+ {
+ if (nTypeArray[j] != NF_SYMBOLTYPE_EMPTY)
+ {
+ pInfo->sStrArray[i] = sStrArray[j];
+ pInfo->nTypeArray[i] = nTypeArray[j];
+ i++;
+ }
+ j++;
+ }
+ pInfo->eScannedType = eScannedType;
+ pInfo->bThousand = bThousand;
+ pInfo->nThousand = nThousand;
+ pInfo->nCntPre = nCntPre;
+ pInfo->nCntPost = nCntPost;
+ pInfo->nCntExp = nCntExp;
+}
+
+bool ImpSvNumberformatScan::ReplaceBooleanEquivalent( OUString& rString )
+{
+ InitKeywords();
+ /* TODO: compare case insensitive? Or rather leave as is and case not
+ * matching indicates user supplied on purpose? Written to file / generated
+ * was always uppercase. */
+ if (rString == sBooleanEquivalent1 || rString == sBooleanEquivalent2)
+ {
+ rString = GetKeywords()[NF_KEY_BOOLEAN];
+ return true;
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
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 <i18nlangtag/lang.h>
+#include <rtl/ustring.hxx>
+#include <svl/nfkeytab.hxx>
+#include <svl/nfsymbol.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <tools/color.hxx>
+#include <tools/date.hxx>
+#include <unotools/localedatawrapper.hxx>
+
+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<Color> & 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<Color> 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: */