summaryrefslogtreecommitdiffstats
path: root/unotools/source/i18n/localedatawrapper.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'unotools/source/i18n/localedatawrapper.cxx')
-rw-r--r--unotools/source/i18n/localedatawrapper.cxx1562
1 files changed, 1562 insertions, 0 deletions
diff --git a/unotools/source/i18n/localedatawrapper.cxx b/unotools/source/i18n/localedatawrapper.cxx
new file mode 100644
index 000000000..25a3fc2f5
--- /dev/null
+++ b/unotools/source/i18n/localedatawrapper.cxx
@@ -0,0 +1,1562 @@
+/* -*- 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 <limits>
+#include <stdio.h>
+#include <string>
+
+#include <sal/log.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <unotools/digitgroupingiterator.hxx>
+#include <tools/diagnose_ex.h>
+#include <tools/debug.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <o3tl/safeint.hxx>
+
+#include <com/sun/star/i18n/KNumberFormatUsage.hpp>
+#include <com/sun/star/i18n/KNumberFormatType.hpp>
+#include <com/sun/star/i18n/LocaleData2.hpp>
+#include <com/sun/star/i18n/NumberFormatIndex.hpp>
+#include <com/sun/star/i18n/NumberFormatMapper.hpp>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/math.hxx>
+#include <tools/date.hxx>
+#include <tools/time.hxx>
+#include <o3tl/string_view.hxx>
+#include <utility>
+
+const sal_uInt16 nCurrFormatDefault = 0;
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::i18n;
+using namespace ::com::sun::star::uno;
+
+namespace
+{
+ uno::Sequence< lang::Locale > gInstalledLocales;
+ std::vector< LanguageType > gInstalledLanguageTypes;
+}
+
+sal_uInt8 LocaleDataWrapper::nLocaleDataChecking = 0;
+
+LocaleDataWrapper::LocaleDataWrapper(
+ const Reference< uno::XComponentContext > & rxContext,
+ LanguageTag aLanguageTag
+ )
+ :
+ m_xContext( rxContext ),
+ xLD( LocaleData2::create(rxContext) ),
+ maLanguageTag(std::move( aLanguageTag ))
+{
+ loadData();
+ loadDateAcceptancePatterns({});
+}
+
+LocaleDataWrapper::LocaleDataWrapper(
+ LanguageTag aLanguageTag,
+ const std::vector<OUString> & rOverrideDateAcceptancePatterns
+ )
+ :
+ m_xContext( comphelper::getProcessComponentContext() ),
+ xLD( LocaleData2::create(m_xContext) ),
+ maLanguageTag(std::move( aLanguageTag ))
+{
+ loadData();
+ loadDateAcceptancePatterns(rOverrideDateAcceptancePatterns);
+}
+
+LocaleDataWrapper::~LocaleDataWrapper()
+{
+}
+
+const LanguageTag& LocaleDataWrapper::getLanguageTag() const
+{
+ return maLanguageTag;
+}
+
+const css::lang::Locale& LocaleDataWrapper::getMyLocale() const
+{
+ return maLanguageTag.getLocale();
+}
+
+void LocaleDataWrapper::loadData()
+{
+ const css::lang::Locale& rMyLocale = maLanguageTag.getLocale();
+
+ {
+ const Sequence< Currency2 > aCurrSeq = getAllCurrencies();
+ if ( !aCurrSeq.hasElements() )
+ {
+ if (areChecksEnabled())
+ outputCheckMessage("LocaleDataWrapper::getCurrSymbolsImpl: no currency at all, using ShellsAndPebbles");
+ aCurrSymbol = "ShellsAndPebbles";
+ aCurrBankSymbol = aCurrSymbol;
+ nCurrPositiveFormat = nCurrNegativeFormat = nCurrFormatDefault;
+ nCurrDigits = 2;
+ }
+ else
+ {
+ auto pCurr = std::find_if(aCurrSeq.begin(), aCurrSeq.end(),
+ [](const Currency2& rCurr) { return rCurr.Default; });
+ if ( pCurr == aCurrSeq.end() )
+ {
+ if (areChecksEnabled())
+ {
+ outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrSymbolsImpl: no default currency" ) );
+ }
+ pCurr = aCurrSeq.begin();
+ }
+ aCurrSymbol = pCurr->Symbol;
+ aCurrBankSymbol = pCurr->BankSymbol;
+ nCurrDigits = pCurr->DecimalPlaces;
+ }
+ }
+
+ loadCurrencyFormats();
+
+ {
+ xDefaultCalendar.reset();
+ xSecondaryCalendar.reset();
+ const Sequence< Calendar2 > xCals = getAllCalendars();
+ if (xCals.getLength() > 1)
+ {
+ auto pCal = std::find_if(xCals.begin(), xCals.end(),
+ [](const Calendar2& rCal) { return !rCal.Default; });
+ if (pCal != xCals.end())
+ xSecondaryCalendar = std::make_shared<Calendar2>( *pCal);
+ }
+ auto pCal = xCals.begin();
+ if (xCals.getLength() > 1)
+ {
+ pCal = std::find_if(xCals.begin(), xCals.end(),
+ [](const Calendar2& rCal) { return rCal.Default; });
+ if (pCal == xCals.end())
+ pCal = xCals.begin();
+ }
+ xDefaultCalendar = std::make_shared<Calendar2>( *pCal);
+ }
+
+ loadDateOrders();
+
+ try
+ {
+ aDateAcceptancePatterns = xLD->getDateAcceptancePatterns( rMyLocale );
+ }
+ catch (const Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "unotools.i18n", "getDateAcceptancePatterns" );
+ aDateAcceptancePatterns = {};
+ }
+
+
+ loadDigitGrouping();
+
+ try
+ {
+ aReservedWords = comphelper::sequenceToContainer<std::vector<OUString>>(xLD->getReservedWord( rMyLocale ));
+ }
+ catch ( const Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "unotools.i18n", "getReservedWord" );
+ }
+
+ try
+ {
+ aLocaleDataItem = xLD->getLocaleItem2( rMyLocale );
+ }
+ catch (const Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "unotools.i18n", "getLocaleItem" );
+ static const css::i18n::LocaleDataItem2 aEmptyItem;
+ aLocaleDataItem = aEmptyItem;
+ }
+
+ aLocaleItem[LocaleItem::DATE_SEPARATOR] = aLocaleDataItem.dateSeparator;
+ aLocaleItem[LocaleItem::THOUSAND_SEPARATOR] = aLocaleDataItem.thousandSeparator;
+ aLocaleItem[LocaleItem::DECIMAL_SEPARATOR] = aLocaleDataItem.decimalSeparator;
+ aLocaleItem[LocaleItem::TIME_SEPARATOR] = aLocaleDataItem.timeSeparator;
+ aLocaleItem[LocaleItem::TIME_100SEC_SEPARATOR] = aLocaleDataItem.time100SecSeparator;
+ aLocaleItem[LocaleItem::LIST_SEPARATOR] = aLocaleDataItem.listSeparator;
+ aLocaleItem[LocaleItem::SINGLE_QUOTATION_START] = aLocaleDataItem.quotationStart;
+ aLocaleItem[LocaleItem::SINGLE_QUOTATION_END] = aLocaleDataItem.quotationEnd;
+ aLocaleItem[LocaleItem::DOUBLE_QUOTATION_START] = aLocaleDataItem.doubleQuotationStart;
+ aLocaleItem[LocaleItem::DOUBLE_QUOTATION_END] = aLocaleDataItem.doubleQuotationEnd;
+ aLocaleItem[LocaleItem::MEASUREMENT_SYSTEM] = aLocaleDataItem.measurementSystem;
+ aLocaleItem[LocaleItem::TIME_AM] = aLocaleDataItem.timeAM;
+ aLocaleItem[LocaleItem::TIME_PM] = aLocaleDataItem.timePM;
+ aLocaleItem[LocaleItem::LONG_DATE_DAY_OF_WEEK_SEPARATOR] = aLocaleDataItem.LongDateDayOfWeekSeparator;
+ aLocaleItem[LocaleItem::LONG_DATE_DAY_SEPARATOR] = aLocaleDataItem.LongDateDaySeparator;
+ aLocaleItem[LocaleItem::LONG_DATE_MONTH_SEPARATOR] = aLocaleDataItem.LongDateMonthSeparator;
+ aLocaleItem[LocaleItem::LONG_DATE_YEAR_SEPARATOR] = aLocaleDataItem.LongDateYearSeparator;
+ aLocaleItem[LocaleItem::DECIMAL_SEPARATOR_ALTERNATIVE] = aLocaleDataItem.decimalSeparatorAlternative;
+}
+
+/* FIXME-BCP47: locale data should provide a language tag instead that could be
+ * passed on. */
+css::i18n::LanguageCountryInfo LocaleDataWrapper::getLanguageCountryInfo() const
+{
+ try
+ {
+ return xLD->getLanguageCountryInfo( getMyLocale() );
+ }
+ catch (const Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "unotools.i18n", "getLanguageCountryInfo" );
+ }
+ return css::i18n::LanguageCountryInfo();
+}
+
+const css::i18n::LocaleDataItem2& LocaleDataWrapper::getLocaleItem() const
+{
+ return aLocaleDataItem;
+}
+
+css::uno::Sequence< css::i18n::Currency2 > LocaleDataWrapper::getAllCurrencies() const
+{
+ try
+ {
+ return xLD->getAllCurrencies2( getMyLocale() );
+ }
+ catch (const Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllCurrencies" );
+ }
+ return {};
+}
+
+css::uno::Sequence< css::i18n::FormatElement > LocaleDataWrapper::getAllFormats() const
+{
+ try
+ {
+ return xLD->getAllFormats( getMyLocale() );
+ }
+ catch (const Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllFormats" );
+ }
+ return {};
+}
+
+css::i18n::ForbiddenCharacters LocaleDataWrapper::getForbiddenCharacters() const
+{
+ try
+ {
+ return xLD->getForbiddenCharacters( getMyLocale() );
+ }
+ catch (const Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "unotools.i18n", "getForbiddenCharacters" );
+ }
+ return css::i18n::ForbiddenCharacters();
+}
+
+const css::uno::Sequence< css::lang::Locale > & LocaleDataWrapper::getAllInstalledLocaleNames() const
+{
+ uno::Sequence< lang::Locale > &rInstalledLocales = gInstalledLocales;
+
+ if ( rInstalledLocales.hasElements() )
+ return rInstalledLocales;
+
+ try
+ {
+ rInstalledLocales = xLD->getAllInstalledLocaleNames();
+ }
+ catch ( const Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllInstalledLocaleNames" );
+ }
+ return rInstalledLocales;
+}
+
+// --- Impl and helpers ----------------------------------------------------
+
+// static
+const css::uno::Sequence< css::lang::Locale >& LocaleDataWrapper::getInstalledLocaleNames()
+{
+ const uno::Sequence< lang::Locale > &rInstalledLocales = gInstalledLocales;
+
+ if ( !rInstalledLocales.hasElements() )
+ {
+ LocaleDataWrapper aLDW( ::comphelper::getProcessComponentContext(), LanguageTag( LANGUAGE_SYSTEM) );
+ aLDW.getAllInstalledLocaleNames();
+ }
+ return rInstalledLocales;
+}
+
+// static
+const std::vector< LanguageType >& LocaleDataWrapper::getInstalledLanguageTypes()
+{
+ std::vector< LanguageType > &rInstalledLanguageTypes = gInstalledLanguageTypes;
+
+ if ( !rInstalledLanguageTypes.empty() )
+ return rInstalledLanguageTypes;
+
+ const css::uno::Sequence< css::lang::Locale > xLoc = getInstalledLocaleNames();
+ sal_Int32 nCount = xLoc.getLength();
+ std::vector< LanguageType > xLang;
+ xLang.reserve(nCount);
+ for ( const auto& rLoc : xLoc )
+ {
+ LanguageTag aLanguageTag( rLoc );
+ OUString aDebugLocale;
+ if (areChecksEnabled())
+ {
+ aDebugLocale = aLanguageTag.getBcp47( false);
+ }
+
+ LanguageType eLang = aLanguageTag.getLanguageType( false);
+ if (areChecksEnabled() && eLang == LANGUAGE_DONTKNOW)
+ {
+ OUString aMsg = "ConvertIsoNamesToLanguage: unknown MS-LCID for locale\n" +
+ aDebugLocale;
+ outputCheckMessage(aMsg);
+ }
+
+ if ( eLang == LANGUAGE_NORWEGIAN) // no_NO, not Bokmal (nb_NO), not Nynorsk (nn_NO)
+ eLang = LANGUAGE_DONTKNOW; // don't offer "Unknown" language
+ if ( eLang != LANGUAGE_DONTKNOW )
+ {
+ LanguageTag aBackLanguageTag( eLang);
+ if ( aLanguageTag != aBackLanguageTag )
+ {
+ // In checks, exclude known problems because no MS-LCID defined
+ // and default for Language found.
+ if ( areChecksEnabled()
+ && aDebugLocale != "ar-SD" // Sudan/ar
+ && aDebugLocale != "en-CB" // Caribbean is not a country
+// && aDebugLocale != "en-BG" // ?!? Bulgaria/en
+// && aDebugLocale != "es-BR" // ?!? Brazil/es
+ )
+ {
+ OUStringBuffer aMsg("ConvertIsoNamesToLanguage/ConvertLanguageToIsoNames: ambiguous locale (MS-LCID?)\n");
+ aMsg.append(aDebugLocale);
+ aMsg.append(" -> 0x");
+ aMsg.append(static_cast<sal_Int32>(static_cast<sal_uInt16>(eLang)), 16);
+ aMsg.append(" -> ");
+ aMsg.append(aBackLanguageTag.getBcp47());
+ outputCheckMessage( aMsg.makeStringAndClear() );
+ }
+ eLang = LANGUAGE_DONTKNOW;
+ }
+ }
+ if ( eLang != LANGUAGE_DONTKNOW )
+ xLang.push_back(eLang);
+ }
+ rInstalledLanguageTypes = xLang;
+
+ return rInstalledLanguageTypes;
+}
+
+const OUString& LocaleDataWrapper::getOneLocaleItem( sal_Int16 nItem ) const
+{
+ if ( nItem >= LocaleItem::COUNT2 )
+ {
+ SAL_WARN( "unotools.i18n", "getOneLocaleItem: bounds" );
+ return aLocaleItem[0];
+ }
+ return aLocaleItem[nItem];
+}
+
+const OUString& LocaleDataWrapper::getOneReservedWord( sal_Int16 nWord ) const
+{
+ if ( nWord < 0 || o3tl::make_unsigned(nWord) >= aReservedWords.size() )
+ {
+ SAL_WARN( "unotools.i18n", "getOneReservedWord: bounds" );
+ static const OUString EMPTY;
+ return EMPTY;
+ }
+ return aReservedWords[nWord];
+}
+
+MeasurementSystem LocaleDataWrapper::mapMeasurementStringToEnum( std::u16string_view rMS ) const
+{
+//! TODO: could be cached too
+ if ( o3tl::equalsIgnoreAsciiCase( rMS, u"metric" ) )
+ return MeasurementSystem::Metric;
+//! TODO: other measurement systems? => extend enum MeasurementSystem
+ return MeasurementSystem::US;
+}
+
+bool LocaleDataWrapper::doesSecondaryCalendarUseEC( std::u16string_view rName ) const
+{
+ if (rName.empty())
+ return false;
+
+ // Check language tag first to avoid loading all calendars of this locale.
+ LanguageTag aLoaded( getLoadedLanguageTag());
+ const OUString& aBcp47( aLoaded.getBcp47());
+ // So far determine only by locale, we know for a few.
+ /* TODO: check date format codes? or add to locale data? */
+ if ( aBcp47 != "ja-JP" &&
+ aBcp47 != "lo-LA" &&
+ aBcp47 != "zh-TW")
+ return false;
+
+ if (!xSecondaryCalendar)
+ return false;
+ if (!xSecondaryCalendar->Name.equalsIgnoreAsciiCase( rName))
+ return false;
+
+ return true;
+}
+
+const std::shared_ptr< css::i18n::Calendar2 >& LocaleDataWrapper::getDefaultCalendar() const
+{
+ return xDefaultCalendar;
+}
+
+css::uno::Sequence< css::i18n::CalendarItem2 > const & LocaleDataWrapper::getDefaultCalendarDays() const
+{
+ return getDefaultCalendar()->Days;
+}
+
+css::uno::Sequence< css::i18n::CalendarItem2 > const & LocaleDataWrapper::getDefaultCalendarMonths() const
+{
+ return getDefaultCalendar()->Months;
+}
+
+// --- currencies -----------------------------------------------------
+
+const OUString& LocaleDataWrapper::getCurrSymbol() const
+{
+ return aCurrSymbol;
+}
+
+const OUString& LocaleDataWrapper::getCurrBankSymbol() const
+{
+ return aCurrBankSymbol;
+}
+
+sal_uInt16 LocaleDataWrapper::getCurrPositiveFormat() const
+{
+ return nCurrPositiveFormat;
+}
+
+sal_uInt16 LocaleDataWrapper::getCurrNegativeFormat() const
+{
+ return nCurrNegativeFormat;
+}
+
+sal_uInt16 LocaleDataWrapper::getCurrDigits() const
+{
+ return nCurrDigits;
+}
+
+void LocaleDataWrapper::scanCurrFormatImpl( std::u16string_view rCode,
+ sal_Int32 nStart, sal_Int32& nSign, sal_Int32& nPar,
+ sal_Int32& nNum, sal_Int32& nBlank, sal_Int32& nSym ) const
+{
+ nSign = nPar = nNum = nBlank = nSym = -1;
+ const sal_Unicode* const pStr = rCode.data();
+ const sal_Unicode* const pStop = pStr + rCode.size();
+ const sal_Unicode* p = pStr + nStart;
+ int nInSection = 0;
+ bool bQuote = false;
+ while ( p < pStop )
+ {
+ if ( bQuote )
+ {
+ if ( *p == '"' && *(p-1) != '\\' )
+ bQuote = false;
+ }
+ else
+ {
+ switch ( *p )
+ {
+ case '"' :
+ if ( pStr == p || *(p-1) != '\\' )
+ bQuote = true;
+ break;
+ case '-' :
+ if (!nInSection && nSign == -1)
+ nSign = p - pStr;
+ break;
+ case '(' :
+ if (!nInSection && nPar == -1)
+ nPar = p - pStr;
+ break;
+ case '0' :
+ case '#' :
+ if (!nInSection && nNum == -1)
+ nNum = p - pStr;
+ break;
+ case '[' :
+ nInSection++;
+ break;
+ case ']' :
+ if ( nInSection )
+ {
+ nInSection--;
+ if (!nInSection && nBlank == -1
+ && nSym != -1 && p < pStop-1 && *(p+1) == ' ' )
+ nBlank = p - pStr + 1;
+ }
+ break;
+ case '$' :
+ if (nSym == -1 && nInSection && *(p-1) == '[')
+ {
+ nSym = p - pStr + 1;
+ if (nNum != -1 && *(p-2) == ' ')
+ nBlank = p - pStr - 2;
+ }
+ break;
+ case ';' :
+ if ( !nInSection )
+ p = pStop;
+ break;
+ default:
+ if (!nInSection && nSym == -1 && o3tl::starts_with(rCode.substr(static_cast<sal_Int32>(p - pStr)), aCurrSymbol))
+ { // currency symbol not surrounded by [$...]
+ nSym = p - pStr;
+ if (nBlank == -1 && pStr < p && *(p-1) == ' ')
+ nBlank = p - pStr - 1;
+ p += aCurrSymbol.getLength() - 1;
+ if (nBlank == -1 && p < pStop-2 && *(p+2) == ' ')
+ nBlank = p - pStr + 2;
+ }
+ }
+ }
+ p++;
+ }
+}
+
+void LocaleDataWrapper::loadCurrencyFormats()
+{
+ css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext );
+ uno::Sequence< NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( KNumberFormatUsage::CURRENCY, maLanguageTag.getLocale() );
+ sal_Int32 nCnt = aFormatSeq.getLength();
+ if ( !nCnt )
+ { // bad luck
+ if (areChecksEnabled())
+ {
+ outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrFormatsImpl: no currency formats" ) );
+ }
+ nCurrPositiveFormat = nCurrNegativeFormat = nCurrFormatDefault;
+ return;
+ }
+ // find a negative code (medium preferred) and a default (medium preferred) (not necessarily the same)
+ NumberFormatCode const * const pFormatArr = aFormatSeq.getArray();
+ sal_Int32 nElem, nDef, nNeg, nMedium;
+ nDef = nNeg = nMedium = -1;
+ for ( nElem = 0; nElem < nCnt; nElem++ )
+ {
+ if ( pFormatArr[nElem].Type == KNumberFormatType::MEDIUM )
+ {
+ if ( pFormatArr[nElem].Default )
+ {
+ nDef = nElem;
+ nMedium = nElem;
+ if ( pFormatArr[nElem].Code.indexOf( ';' ) >= 0 )
+ nNeg = nElem;
+ }
+ else
+ {
+ if ( (nNeg == -1 || nMedium == -1) && pFormatArr[nElem].Code.indexOf( ';' ) >= 0 )
+ nNeg = nElem;
+ if ( nMedium == -1 )
+ nMedium = nElem;
+ }
+ }
+ else
+ {
+ if ( nDef == -1 && pFormatArr[nElem].Default )
+ nDef = nElem;
+ if ( nNeg == -1 && pFormatArr[nElem].Code.indexOf( ';' ) >= 0 )
+ nNeg = nElem;
+ }
+ }
+
+ sal_Int32 nSign, nPar, nNum, nBlank, nSym;
+
+ // positive format
+ nElem = (nDef >= 0 ? nDef : (nNeg >= 0 ? nNeg : 0));
+ scanCurrFormatImpl( pFormatArr[nElem].Code, 0, nSign, nPar, nNum, nBlank, nSym );
+ if (areChecksEnabled() && (nNum == -1 || nSym == -1))
+ {
+ outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrFormatsImpl: CurrPositiveFormat?" ) );
+ }
+ if (nBlank == -1)
+ {
+ if ( nSym < nNum )
+ nCurrPositiveFormat = 0; // $1
+ else
+ nCurrPositiveFormat = 1; // 1$
+ }
+ else
+ {
+ if ( nSym < nNum )
+ nCurrPositiveFormat = 2; // $ 1
+ else
+ nCurrPositiveFormat = 3; // 1 $
+ }
+
+ // negative format
+ if ( nNeg < 0 )
+ nCurrNegativeFormat = nCurrFormatDefault;
+ else
+ {
+ const OUString& rCode = pFormatArr[nNeg].Code;
+ sal_Int32 nDelim = rCode.indexOf(';');
+ scanCurrFormatImpl( rCode, nDelim+1, nSign, nPar, nNum, nBlank, nSym );
+ if (areChecksEnabled() && (nNum == -1 || nSym == -1 || (nPar == -1 && nSign == -1)))
+ {
+ outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrFormatsImpl: CurrNegativeFormat?" ) );
+ }
+ // NOTE: one of nPar or nSign are allowed to be -1
+ if (nBlank == -1)
+ {
+ if ( nSym < nNum )
+ {
+ if ( -1 < nPar && nPar < nSym )
+ nCurrNegativeFormat = 0; // ($1)
+ else if ( -1 < nSign && nSign < nSym )
+ nCurrNegativeFormat = 1; // -$1
+ else if ( nNum < nSign )
+ nCurrNegativeFormat = 3; // $1-
+ else
+ nCurrNegativeFormat = 2; // $-1
+ }
+ else
+ {
+ if ( -1 < nPar && nPar < nNum )
+ nCurrNegativeFormat = 4; // (1$)
+ else if ( -1 < nSign && nSign < nNum )
+ nCurrNegativeFormat = 5; // -1$
+ else if ( nSym < nSign )
+ nCurrNegativeFormat = 7; // 1$-
+ else
+ nCurrNegativeFormat = 6; // 1-$
+ }
+ }
+ else
+ {
+ if ( nSym < nNum )
+ {
+ if ( -1 < nPar && nPar < nSym )
+ nCurrNegativeFormat = 14; // ($ 1)
+ else if ( -1 < nSign && nSign < nSym )
+ nCurrNegativeFormat = 9; // -$ 1
+ else if ( nNum < nSign )
+ nCurrNegativeFormat = 12; // $ 1-
+ else
+ nCurrNegativeFormat = 11; // $ -1
+ }
+ else
+ {
+ if ( -1 < nPar && nPar < nNum )
+ nCurrNegativeFormat = 15; // (1 $)
+ else if ( -1 < nSign && nSign < nNum )
+ nCurrNegativeFormat = 8; // -1 $
+ else if ( nSym < nSign )
+ nCurrNegativeFormat = 10; // 1 $-
+ else
+ nCurrNegativeFormat = 13; // 1- $
+ }
+ }
+ }
+}
+
+// --- date -----------------------------------------------------------
+
+DateOrder LocaleDataWrapper::getDateOrder() const
+{
+ return nDateOrder;
+}
+
+LongDateOrder LocaleDataWrapper::getLongDateOrder() const
+{
+ return nLongDateOrder;
+}
+
+LongDateOrder LocaleDataWrapper::scanDateOrderImpl( std::u16string_view rCode ) const
+{
+ // Only some european versions were translated, the ones with different
+ // keyword combinations are:
+ // English DMY, German TMJ, Spanish DMA, French JMA, Italian GMA,
+ // Dutch DMJ, Finnish PKV
+
+ // default is English keywords for every other language
+ size_t nDay = rCode.find('D');
+ size_t nMonth = rCode.find('M');
+ size_t nYear = rCode.find('Y');
+ if (nDay == std::u16string_view::npos || nMonth == std::u16string_view::npos || nYear == std::u16string_view::npos)
+ { // This algorithm assumes that all three parts (DMY) are present
+ if (nMonth == std::u16string_view::npos)
+ { // only Finnish has something else than 'M' for month
+ nMonth = rCode.find('K');
+ if (nMonth != std::u16string_view::npos)
+ {
+ nDay = rCode.find('P');
+ nYear = rCode.find('V');
+ }
+ }
+ else if (nDay == std::u16string_view::npos)
+ { // We have a month 'M' if we reach this branch.
+ // Possible languages containing 'M' but no 'D':
+ // German, French, Italian
+ nDay = rCode.find('T'); // German
+ if (nDay != std::u16string_view::npos)
+ nYear = rCode.find('J');
+ else
+ {
+ nYear = rCode.find('A'); // French, Italian
+ if (nYear != std::u16string_view::npos)
+ {
+ nDay = rCode.find('J'); // French
+ if (nDay == std::u16string_view::npos)
+ nDay = rCode.find('G'); // Italian
+ }
+ }
+ }
+ else
+ { // We have a month 'M' and a day 'D'.
+ // Possible languages containing 'D' and 'M' but not 'Y':
+ // Spanish, Dutch
+ nYear = rCode.find('A'); // Spanish
+ if (nYear == std::u16string_view::npos)
+ nYear = rCode.find('J'); // Dutch
+ }
+ if (nDay == std::u16string_view::npos || nMonth == std::u16string_view::npos || nYear == std::u16string_view::npos)
+ {
+ if (areChecksEnabled())
+ {
+ outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::scanDateOrder: not all DMY present" ) );
+ }
+ if (nDay == std::u16string_view::npos)
+ nDay = rCode.size();
+ if (nMonth == std::u16string_view::npos)
+ nMonth = rCode.size();
+ if (nYear == std::u16string_view::npos)
+ nYear = rCode.size();
+ }
+ }
+ // compare with <= because each position may equal rCode.getLength()
+ if ( nDay <= nMonth && nMonth <= nYear )
+ return LongDateOrder::DMY; // also if every position equals rCode.getLength()
+ else if ( nMonth <= nDay && nDay <= nYear )
+ return LongDateOrder::MDY;
+ else if ( nYear <= nMonth && nMonth <= nDay )
+ return LongDateOrder::YMD;
+ else if ( nYear <= nDay && nDay <= nMonth )
+ return LongDateOrder::YDM;
+ else
+ {
+ if (areChecksEnabled())
+ {
+ outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::scanDateOrder: no magic applicable" ) );
+ }
+ return LongDateOrder::DMY;
+ }
+}
+
+static DateOrder getDateOrderFromLongDateOrder( LongDateOrder eLong )
+{
+ switch (eLong)
+ {
+ case LongDateOrder::YMD:
+ return DateOrder::YMD;
+ break;
+ case LongDateOrder::DMY:
+ return DateOrder::DMY;
+ break;
+ case LongDateOrder::MDY:
+ return DateOrder::MDY;
+ break;
+ case LongDateOrder::YDM:
+ default:
+ assert(!"unhandled LongDateOrder to DateOrder");
+ return DateOrder::DMY;
+ }
+}
+
+void LocaleDataWrapper::loadDateOrders()
+{
+ css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext );
+ uno::Sequence< NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( KNumberFormatUsage::DATE, maLanguageTag.getLocale() );
+ sal_Int32 nCnt = aFormatSeq.getLength();
+ if ( !nCnt )
+ { // bad luck
+ if (areChecksEnabled())
+ {
+ outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getDateOrdersImpl: no date formats" ) );
+ }
+ nDateOrder = DateOrder::DMY;
+ nLongDateOrder = LongDateOrder::DMY;
+ return;
+ }
+ // find the edit (21), a default (medium preferred),
+ // a medium (default preferred), and a long (default preferred)
+ NumberFormatCode const * const pFormatArr = aFormatSeq.getArray();
+ sal_Int32 nEdit, nDef, nMedium, nLong;
+ nEdit = nDef = nMedium = nLong = -1;
+ for ( sal_Int32 nElem = 0; nElem < nCnt; nElem++ )
+ {
+ if ( nEdit == -1 && pFormatArr[nElem].Index == NumberFormatIndex::DATE_SYS_DDMMYYYY )
+ nEdit = nElem;
+ if ( nDef == -1 && pFormatArr[nElem].Default )
+ nDef = nElem;
+ switch ( pFormatArr[nElem].Type )
+ {
+ case KNumberFormatType::MEDIUM :
+ {
+ if ( pFormatArr[nElem].Default )
+ {
+ nDef = nElem;
+ nMedium = nElem;
+ }
+ else if ( nMedium == -1 )
+ nMedium = nElem;
+ }
+ break;
+ case KNumberFormatType::LONG :
+ {
+ if ( pFormatArr[nElem].Default )
+ nLong = nElem;
+ else if ( nLong == -1 )
+ nLong = nElem;
+ }
+ break;
+ }
+ }
+ if ( nEdit == -1 )
+ {
+ if (areChecksEnabled())
+ {
+ outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getDateOrdersImpl: no edit" ) );
+ }
+ if ( nDef == -1 )
+ {
+ if (areChecksEnabled())
+ {
+ outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getDateOrdersImpl: no default" ) );
+ }
+ if ( nMedium != -1 )
+ nDef = nMedium;
+ else if ( nLong != -1 )
+ nDef = nLong;
+ else
+ nDef = 0;
+ }
+ nEdit = nDef;
+ }
+ LongDateOrder nDO = scanDateOrderImpl( pFormatArr[nEdit].Code );
+ if ( pFormatArr[nEdit].Type == KNumberFormatType::LONG )
+ { // normally this is not the case
+ nLongDateOrder = nDO;
+ nDateOrder = getDateOrderFromLongDateOrder(nDO);
+ }
+ else
+ {
+ // YDM should not occur in a short/medium date (i.e. no locale has
+ // that) and is nowhere handled.
+ nDateOrder = getDateOrderFromLongDateOrder(nDO);
+ if ( nLong == -1 )
+ nLongDateOrder = nDO;
+ else
+ nLongDateOrder = scanDateOrderImpl( pFormatArr[nLong].Code );
+ }
+}
+
+// --- digit grouping -------------------------------------------------
+
+void LocaleDataWrapper::loadDigitGrouping()
+{
+ /* TODO: This is a very simplified grouping setup that only serves its
+ * current purpose for Indian locales. A free-form flexible one would
+ * obtain grouping from locale data where it could be specified using, for
+ * example, codes like #,### and #,##,### that would generate the integer
+ * sequence. Needed additional API and a locale data element.
+ */
+
+ if (aGrouping.hasElements() && aGrouping[0])
+ return;
+
+ i18n::LanguageCountryInfo aLCInfo( getLanguageCountryInfo());
+ if (aLCInfo.Country.equalsIgnoreAsciiCase("IN") || // India
+ aLCInfo.Country.equalsIgnoreAsciiCase("BT") ) // Bhutan
+ {
+ aGrouping = { 3, 2, 0 };
+ }
+ else
+ {
+ aGrouping = { 3, 0, 0 };
+ }
+}
+
+const css::uno::Sequence< sal_Int32 >& LocaleDataWrapper::getDigitGrouping() const
+{
+ return aGrouping;
+}
+
+// --- simple number formatting helpers -------------------------------
+
+// The ImplAdd... methods are taken from class International and modified to
+// suit the needs.
+
+static void ImplAddUNum( OUStringBuffer& rBuf, sal_uInt64 nNumber )
+{
+ // fill temp buffer with digits
+ sal_Unicode aTempBuf[64];
+ sal_Unicode* pTempBuf = aTempBuf;
+ do
+ {
+ *pTempBuf = static_cast<sal_Unicode>(nNumber % 10) + '0';
+ pTempBuf++;
+ nNumber /= 10;
+ }
+ while ( nNumber );
+
+ // copy temp buffer to buffer passed
+ do
+ {
+ pTempBuf--;
+ rBuf.append(*pTempBuf);
+ }
+ while ( pTempBuf != aTempBuf );
+}
+
+static void ImplAddUNum( OUStringBuffer& rBuf, sal_uInt64 nNumber, int nMinLen )
+{
+ // fill temp buffer with digits
+ sal_Unicode aTempBuf[64];
+ sal_Unicode* pTempBuf = aTempBuf;
+ do
+ {
+ *pTempBuf = static_cast<sal_Unicode>(nNumber % 10) + '0';
+ pTempBuf++;
+ nNumber /= 10;
+ nMinLen--;
+ }
+ while ( nNumber );
+
+ // fill with zeros up to the minimal length
+ while ( nMinLen > 0 )
+ {
+ rBuf.append('0');
+ nMinLen--;
+ }
+
+ // copy temp buffer to real buffer
+ do
+ {
+ pTempBuf--;
+ rBuf.append(*pTempBuf);
+ }
+ while ( pTempBuf != aTempBuf );
+}
+
+static void ImplAddNum( OUStringBuffer& rBuf, sal_Int64 nNumber, int nMinLen )
+{
+ if (nNumber < 0)
+ {
+ rBuf.append('-');
+ nNumber = -nNumber;
+ }
+ return ImplAddUNum( rBuf, nNumber, nMinLen);
+}
+
+static void ImplAdd2UNum( OUStringBuffer& rBuf, sal_uInt16 nNumber )
+{
+ DBG_ASSERT( nNumber < 100, "ImplAdd2UNum() - Number >= 100" );
+
+ if ( nNumber < 10 )
+ {
+ rBuf.append('0');
+ rBuf.append(static_cast<char>(nNumber + '0'));
+ }
+ else
+ {
+ sal_uInt16 nTemp = nNumber % 10;
+ nNumber /= 10;
+ rBuf.append(static_cast<char>(nNumber + '0'));
+ rBuf.append(static_cast<char>(nTemp + '0'));
+ }
+}
+
+static void ImplAdd9UNum( OUStringBuffer& rBuf, sal_uInt32 nNumber )
+{
+ DBG_ASSERT( nNumber < 1000000000, "ImplAdd9UNum() - Number >= 1000000000" );
+
+ std::ostringstream ostr;
+ ostr.fill('0');
+ ostr.width(9);
+ ostr << nNumber;
+ std::string aStr = ostr.str();
+ rBuf.appendAscii(aStr.c_str(), aStr.size());
+}
+
+void LocaleDataWrapper::ImplAddFormatNum( OUStringBuffer& rBuf,
+ sal_Int64 nNumber, sal_uInt16 nDecimals, bool bUseThousandSep,
+ bool bTrailingZeros ) const
+{
+ OUStringBuffer aNumBuf(64);
+ sal_uInt16 nNumLen;
+
+ // negative number
+ sal_uInt64 abs;
+ if ( nNumber < 0 )
+ {
+ // Avoid overflow, map -2^63 -> 2^63 explicitly:
+ abs = nNumber == std::numeric_limits<sal_Int64>::min()
+ ? static_cast<sal_uInt64>(std::numeric_limits<sal_Int64>::min()) : nNumber * -1;
+ rBuf.append('-');
+ }
+ else
+ {
+ abs = nNumber;
+ }
+
+ // convert number
+ ImplAddUNum( aNumBuf, abs );
+ nNumLen = static_cast<sal_uInt16>(aNumBuf.getLength());
+
+ if ( nNumLen <= nDecimals )
+ {
+ // strip .0 in decimals?
+ if ( !nNumber && !bTrailingZeros )
+ {
+ rBuf.append('0');
+ }
+ else
+ {
+ // LeadingZero, insert 0
+ if ( isNumLeadingZero() )
+ {
+ rBuf.append('0');
+ }
+
+ // append decimal separator
+ rBuf.append( aLocaleDataItem.decimalSeparator );
+
+ // fill with zeros
+ sal_uInt16 i = 0;
+ while ( i < (nDecimals-nNumLen) )
+ {
+ rBuf.append('0');
+ i++;
+ }
+
+ // append decimals
+ rBuf.append(aNumBuf);
+ }
+ }
+ else
+ {
+ const OUString& rThoSep = aLocaleDataItem.thousandSeparator;
+
+ // copy number to buffer (excluding decimals)
+ sal_uInt16 nNumLen2 = nNumLen-nDecimals;
+ uno::Sequence< sal_Bool > aGroupPos;
+ if (bUseThousandSep)
+ aGroupPos = utl::DigitGroupingIterator::createForwardSequence(
+ nNumLen2, getDigitGrouping());
+ sal_uInt16 i = 0;
+ for (; i < nNumLen2; ++i )
+ {
+ rBuf.append(aNumBuf[i]);
+
+ // add thousand separator?
+ if ( bUseThousandSep && aGroupPos[i] )
+ rBuf.append( rThoSep );
+ }
+
+ // append decimals
+ if ( nDecimals )
+ {
+ rBuf.append( aLocaleDataItem.decimalSeparator );
+
+ bool bNullEnd = true;
+ while ( i < nNumLen )
+ {
+ if ( aNumBuf[i] != '0' )
+ bNullEnd = false;
+
+ rBuf.append(aNumBuf[i]);
+ i++;
+ }
+
+ // strip .0 in decimals?
+ if ( bNullEnd && !bTrailingZeros )
+ rBuf.setLength( rBuf.getLength() - (nDecimals + 1) );
+ }
+ }
+}
+
+// --- simple date and time formatting --------------------------------
+
+OUString LocaleDataWrapper::getDate( const Date& rDate ) const
+{
+//!TODO: leading zeros et al
+ OUStringBuffer aBuf(128);
+ sal_uInt16 nDay = rDate.GetDay();
+ sal_uInt16 nMonth = rDate.GetMonth();
+ sal_Int16 nYear = rDate.GetYear();
+ sal_uInt16 nYearLen;
+
+ if ( (true) /* IsDateCentury() */ )
+ nYearLen = 4;
+ else
+ {
+ nYearLen = 2;
+ nYear %= 100;
+ }
+
+ switch ( getDateOrder() )
+ {
+ case DateOrder::DMY :
+ ImplAdd2UNum( aBuf, nDay );
+ aBuf.append( aLocaleDataItem.dateSeparator );
+ ImplAdd2UNum( aBuf, nMonth );
+ aBuf.append( aLocaleDataItem.dateSeparator );
+ ImplAddNum( aBuf, nYear, nYearLen );
+ break;
+ case DateOrder::MDY :
+ ImplAdd2UNum( aBuf, nMonth );
+ aBuf.append( aLocaleDataItem.dateSeparator );
+ ImplAdd2UNum( aBuf, nDay );
+ aBuf.append( aLocaleDataItem.dateSeparator );
+ ImplAddNum( aBuf, nYear, nYearLen );
+ break;
+ default:
+ ImplAddNum( aBuf, nYear, nYearLen );
+ aBuf.append( aLocaleDataItem.dateSeparator );
+ ImplAdd2UNum( aBuf, nMonth );
+ aBuf.append( aLocaleDataItem.dateSeparator );
+ ImplAdd2UNum( aBuf, nDay );
+ }
+
+ return aBuf.makeStringAndClear();
+}
+
+OUString LocaleDataWrapper::getTime( const tools::Time& rTime, bool bSec, bool b100Sec ) const
+{
+//!TODO: leading zeros et al
+ OUStringBuffer aBuf(128);
+ sal_uInt16 nHour = rTime.GetHour();
+
+ nHour %= 24;
+
+ ImplAdd2UNum( aBuf, nHour );
+ aBuf.append( aLocaleDataItem.timeSeparator );
+ ImplAdd2UNum( aBuf, rTime.GetMin() );
+ if ( bSec )
+ {
+ aBuf.append( aLocaleDataItem.timeSeparator );
+ ImplAdd2UNum( aBuf, rTime.GetSec() );
+
+ if ( b100Sec )
+ {
+ aBuf.append( aLocaleDataItem.time100SecSeparator );
+ ImplAdd9UNum( aBuf, rTime.GetNanoSec() );
+ }
+ }
+
+ return aBuf.makeStringAndClear();
+}
+
+OUString LocaleDataWrapper::getDuration( const tools::Time& rTime, bool bSec, bool b100Sec ) const
+{
+ OUStringBuffer aBuf(128);
+
+ if ( rTime < tools::Time( 0 ) )
+ aBuf.append(' ' );
+
+ if ( (true) /* IsTimeLeadingZero() */ )
+ ImplAddUNum( aBuf, rTime.GetHour(), 2 );
+ else
+ ImplAddUNum( aBuf, rTime.GetHour() );
+ aBuf.append( aLocaleDataItem.timeSeparator );
+ ImplAdd2UNum( aBuf, rTime.GetMin() );
+ if ( bSec )
+ {
+ aBuf.append( aLocaleDataItem.timeSeparator );
+ ImplAdd2UNum( aBuf, rTime.GetSec() );
+
+ if ( b100Sec )
+ {
+ aBuf.append( aLocaleDataItem.time100SecSeparator );
+ ImplAdd9UNum( aBuf, rTime.GetNanoSec() );
+ }
+ }
+
+ return aBuf.makeStringAndClear();
+}
+
+// --- simple number formatting ---------------------------------------
+
+static size_t ImplGetNumberStringLengthGuess( const css::i18n::LocaleDataItem2& rLocaleDataItem, sal_uInt16 nDecimals )
+{
+ // approximately 3.2 bits per digit
+ const size_t nDig = ((sizeof(sal_Int64) * 8) / 3) + 1;
+ // digits, separators (pessimized for insane "every digit may be grouped"), leading zero, sign
+ size_t nGuess = ((nDecimals < nDig) ?
+ (((nDig - nDecimals) * rLocaleDataItem.thousandSeparator.getLength()) + nDig) :
+ nDecimals) + rLocaleDataItem.decimalSeparator.getLength() + 3;
+ return nGuess;
+}
+
+OUString LocaleDataWrapper::getNum( sal_Int64 nNumber, sal_uInt16 nDecimals,
+ bool bUseThousandSep, bool bTrailingZeros ) const
+{
+ // check if digits and separators will fit into fixed buffer or allocate
+ size_t nGuess = ImplGetNumberStringLengthGuess( aLocaleDataItem, nDecimals );
+ OUStringBuffer aBuf(int(nGuess + 16));
+
+ ImplAddFormatNum( aBuf, nNumber, nDecimals,
+ bUseThousandSep, bTrailingZeros );
+
+ return aBuf.makeStringAndClear();
+}
+
+OUString LocaleDataWrapper::getCurr( sal_Int64 nNumber, sal_uInt16 nDecimals,
+ std::u16string_view rCurrencySymbol, bool bUseThousandSep ) const
+{
+ sal_Unicode cZeroChar = getCurrZeroChar();
+
+ // check if digits and separators will fit into fixed buffer or allocate
+ size_t nGuess = ImplGetNumberStringLengthGuess( aLocaleDataItem, nDecimals );
+ OUStringBuffer aNumBuf(int(nGuess + 16));
+ OUStringBuffer aBuf(int(rCurrencySymbol.size() + nGuess + 20 ));
+
+ bool bNeg;
+ if ( nNumber < 0 )
+ {
+ bNeg = true;
+ nNumber *= -1;
+ }
+ else
+ bNeg = false;
+
+ // convert number
+ ImplAddFormatNum( aNumBuf, nNumber, nDecimals,
+ bUseThousandSep, true );
+ const sal_Int32 nNumLen = aNumBuf.getLength();
+
+ // replace zeros with zero character
+ if ( (cZeroChar != '0') && nDecimals /* && IsNumTrailingZeros() */ )
+ {
+ sal_uInt16 i;
+ bool bZero = true;
+
+ sal_uInt16 nNumBufIndex = nNumLen-nDecimals;
+ i = 0;
+ do
+ {
+ if ( aNumBuf[nNumBufIndex] != '0' )
+ {
+ bZero = false;
+ break;
+ }
+
+ nNumBufIndex++;
+ i++;
+ }
+ while ( i < nDecimals );
+
+ if ( bZero )
+ {
+ nNumBufIndex = nNumLen-nDecimals;
+ i = 0;
+ do
+ {
+ aNumBuf[nNumBufIndex] = cZeroChar;
+ nNumBufIndex++;
+ i++;
+ }
+ while ( i < nDecimals );
+ }
+ }
+
+ if ( !bNeg )
+ {
+ switch( getCurrPositiveFormat() )
+ {
+ case 0:
+ aBuf.append( rCurrencySymbol );
+ aBuf.append( aNumBuf );
+ break;
+ case 1:
+ aBuf.append( aNumBuf );
+ aBuf.append( rCurrencySymbol );
+ break;
+ case 2:
+ aBuf.append( rCurrencySymbol );
+ aBuf.append( ' ' );
+ aBuf.append( aNumBuf );
+ break;
+ case 3:
+ aBuf.append( aNumBuf );
+ aBuf.append( ' ' );
+ aBuf.append( rCurrencySymbol );
+ break;
+ }
+ }
+ else
+ {
+ switch( getCurrNegativeFormat() )
+ {
+ case 0:
+ aBuf.append( '(' );
+ aBuf.append( rCurrencySymbol );
+ aBuf.append( aNumBuf );
+ aBuf.append( ')' );
+ break;
+ case 1:
+ aBuf.append( '-' );
+ aBuf.append( rCurrencySymbol );
+ aBuf.append( aNumBuf );
+ break;
+ case 2:
+ aBuf.append( rCurrencySymbol );
+ aBuf.append( '-' );
+ aBuf.append( aNumBuf );
+ break;
+ case 3:
+ aBuf.append( rCurrencySymbol );
+ aBuf.append( aNumBuf );
+ aBuf.append( '-' );
+ break;
+ case 4:
+ aBuf.append( '(' );
+ aBuf.append( aNumBuf );
+ aBuf.append( rCurrencySymbol );
+ aBuf.append( ')' );
+ break;
+ case 5:
+ aBuf.append( '-' );
+ aBuf.append( aNumBuf );
+ aBuf.append( rCurrencySymbol );
+ break;
+ case 6:
+ aBuf.append( aNumBuf );
+ aBuf.append( '-' );
+ aBuf.append( rCurrencySymbol );
+ break;
+ case 7:
+ aBuf.append( aNumBuf );
+ aBuf.append( rCurrencySymbol );
+ aBuf.append( '-' );
+ break;
+ case 8:
+ aBuf.append( '-' );
+ aBuf.append( aNumBuf );
+ aBuf.append( ' ' );
+ aBuf.append( rCurrencySymbol );
+ break;
+ case 9:
+ aBuf.append( '-' );
+ aBuf.append( rCurrencySymbol );
+ aBuf.append( ' ' );
+ aBuf.append( aNumBuf );
+ break;
+ case 10:
+ aBuf.append( aNumBuf );
+ aBuf.append( ' ' );
+ aBuf.append( rCurrencySymbol );
+ aBuf.append( '-' );
+ break;
+ case 11:
+ aBuf.append( rCurrencySymbol );
+ aBuf.append( ' ' );
+ aBuf.append( '-' );
+ aBuf.append( aNumBuf );
+ break;
+ case 12:
+ aBuf.append( rCurrencySymbol );
+ aBuf.append( ' ' );
+ aBuf.append( aNumBuf );
+ aBuf.append( '-' );
+ break;
+ case 13:
+ aBuf.append( aNumBuf );
+ aBuf.append( '-' );
+ aBuf.append( ' ' );
+ aBuf.append( rCurrencySymbol );
+ break;
+ case 14:
+ aBuf.append( '(' );
+ aBuf.append( rCurrencySymbol );
+ aBuf.append( ' ' );
+ aBuf.append( aNumBuf );
+ aBuf.append( ')' );
+ break;
+ case 15:
+ aBuf.append( '(' );
+ aBuf.append( aNumBuf );
+ aBuf.append( ' ' );
+ aBuf.append( rCurrencySymbol );
+ aBuf.append( ')' );
+ break;
+ }
+ }
+
+ return aBuf.makeStringAndClear();
+}
+
+// --- number parsing -------------------------------------------------
+
+double LocaleDataWrapper::stringToDouble( std::u16string_view aString, bool bUseGroupSep,
+ rtl_math_ConversionStatus* pStatus, sal_Int32* pParseEnd ) const
+{
+ const sal_Unicode* pParseEndChar;
+ double fValue = stringToDouble(aString.data(), aString.data() + aString.size(), bUseGroupSep, pStatus, &pParseEndChar);
+ if (pParseEnd)
+ *pParseEnd = pParseEndChar - aString.data();
+ return fValue;
+}
+
+double LocaleDataWrapper::stringToDouble( const sal_Unicode* pBegin, const sal_Unicode* pEnd, bool bUseGroupSep,
+ rtl_math_ConversionStatus* pStatus, const sal_Unicode** ppParseEnd ) const
+{
+ const sal_Unicode cGroupSep = (bUseGroupSep ? aLocaleDataItem.thousandSeparator[0] : 0);
+ rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
+ const sal_Unicode* pParseEnd = nullptr;
+ double fValue = rtl_math_uStringToDouble( pBegin, pEnd, aLocaleDataItem.decimalSeparator[0], cGroupSep, &eStatus, &pParseEnd);
+ bool bTryAlt = (pParseEnd < pEnd && !aLocaleDataItem.decimalSeparatorAlternative.isEmpty() &&
+ *pParseEnd == aLocaleDataItem.decimalSeparatorAlternative.toChar());
+ // Try re-parsing with alternative if that was the reason to stop.
+ if (bTryAlt)
+ fValue = rtl_math_uStringToDouble( pBegin, pEnd, aLocaleDataItem.decimalSeparatorAlternative.toChar(), cGroupSep, &eStatus, &pParseEnd);
+ if (pStatus)
+ *pStatus = eStatus;
+ if (ppParseEnd)
+ *ppParseEnd = pParseEnd;
+ return fValue;
+}
+
+// --- mixed ----------------------------------------------------------
+
+LanguageTag LocaleDataWrapper::getLoadedLanguageTag() const
+{
+ LanguageCountryInfo aLCInfo = getLanguageCountryInfo();
+ return LanguageTag( lang::Locale( aLCInfo.Language, aLCInfo.Country, aLCInfo.Variant ));
+}
+
+OUString LocaleDataWrapper::appendLocaleInfo(std::u16string_view rDebugMsg) const
+{
+ LanguageTag aLoaded = getLoadedLanguageTag();
+ return OUString::Concat(rDebugMsg) + "\n" + maLanguageTag.getBcp47() + " requested\n"
+ + aLoaded.getBcp47() + " loaded";
+}
+
+// static
+void LocaleDataWrapper::outputCheckMessage( std::u16string_view rMsg )
+{
+ outputCheckMessage(OUStringToOString(rMsg, RTL_TEXTENCODING_UTF8).getStr());
+}
+
+// static
+void LocaleDataWrapper::outputCheckMessage( const char* pStr )
+{
+ fprintf( stderr, "\n%s\n", pStr);
+ fflush( stderr);
+ SAL_WARN("unotools.i18n", pStr);
+}
+
+// static
+void LocaleDataWrapper::evaluateLocaleDataChecking()
+{
+ // Using the rtl_Instance template here wouldn't solve all threaded write
+ // accesses, since we want to assign the result to the static member
+ // variable and would need to dereference the pointer returned and assign
+ // the value unguarded. This is the same pattern manually coded.
+ sal_uInt8 nCheck = nLocaleDataChecking;
+ if (!nCheck)
+ {
+ ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex());
+ nCheck = nLocaleDataChecking;
+ if (!nCheck)
+ {
+#ifdef DBG_UTIL
+ nCheck = 1;
+#else
+ const char* pEnv = getenv( "OOO_ENABLE_LOCALE_DATA_CHECKS");
+ if (pEnv && (pEnv[0] == 'Y' || pEnv[0] == 'y' || pEnv[0] == '1'))
+ nCheck = 1;
+ else
+ nCheck = 2;
+#endif
+ OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
+ nLocaleDataChecking = nCheck;
+ }
+ }
+ else {
+ OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
+ }
+}
+
+// --- XLocaleData3 ----------------------------------------------------------
+
+css::uno::Sequence< css::i18n::Calendar2 > LocaleDataWrapper::getAllCalendars() const
+{
+ try
+ {
+ return xLD->getAllCalendars2( getMyLocale() );
+ }
+ catch (const Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllCalendars" );
+ }
+ return {};
+}
+
+// --- XLocaleData4 ----------------------------------------------------------
+
+const css::uno::Sequence< OUString > & LocaleDataWrapper::getDateAcceptancePatterns() const
+{
+ return aDateAcceptancePatterns;
+}
+
+// --- Override layer --------------------------------------------------------
+
+void LocaleDataWrapper::loadDateAcceptancePatterns(
+ const std::vector<OUString> & rPatterns )
+{
+ if (!aDateAcceptancePatterns.hasElements() || rPatterns.empty())
+ {
+ try
+ {
+ aDateAcceptancePatterns = xLD->getDateAcceptancePatterns( maLanguageTag.getLocale() );
+ }
+ catch (const Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "unotools.i18n", "setDateAcceptancePatterns" );
+ }
+ if (rPatterns.empty())
+ return; // just a reset
+ if (!aDateAcceptancePatterns.hasElements())
+ {
+ aDateAcceptancePatterns = comphelper::containerToSequence(rPatterns);
+ return;
+ }
+ }
+
+ // Earlier versions checked for presence of the full date pattern with
+ // aDateAcceptancePatterns[0] == rPatterns[0] and prepended that if not.
+ // This lead to confusion if the patterns were intentionally specified
+ // without, giving entirely a different DMY order, see tdf#150288.
+ // Not checking this and accepting the given patterns as is may result in
+ // the user shooting themself in the foot, but we can't have both.
+ aDateAcceptancePatterns = comphelper::containerToSequence(rPatterns);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */