b759f6720b
... if it wasn't present as first, but take the date acceptance patterns as specified by the user. Change-Id: Ife2fd39731bac0e0b121f22392f22b48ffc9c978 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/138694 Reviewed-by: Eike Rathke <erack@redhat.com> Tested-by: Jenkins
1562 lines
49 KiB
C++
1562 lines
49 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include <limits>
|
|
#include <stdio.h>
|
|
#include <string>
|
|
|
|
#include <sal/log.hxx>
|
|
#include <unotools/localedatawrapper.hxx>
|
|
#include <unotools/digitgroupingiterator.hxx>
|
|
#include <comphelper/diagnose_ex.hxx>
|
|
#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 );
|
|
}
|
|
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: */
|