Coverage Report

Created: 2025-12-31 10:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/unotools/source/i18n/localedatawrapper.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <limits>
21
#include <stdio.h>
22
#include <string>
23
24
#include <sal/log.hxx>
25
#include <unotools/localedatawrapper.hxx>
26
#include <unotools/digitgroupingiterator.hxx>
27
#include <comphelper/diagnose_ex.hxx>
28
#include <tools/debug.hxx>
29
#include <i18nlangtag/languagetag.hxx>
30
#include <o3tl/safeint.hxx>
31
32
#include <com/sun/star/i18n/KNumberFormatUsage.hpp>
33
#include <com/sun/star/i18n/KNumberFormatType.hpp>
34
#include <com/sun/star/i18n/LocaleData2.hpp>
35
#include <com/sun/star/i18n/NumberFormatIndex.hpp>
36
#include <com/sun/star/i18n/NumberFormatMapper.hpp>
37
38
#include <comphelper/processfactory.hxx>
39
#include <comphelper/sequence.hxx>
40
#include <rtl/ustrbuf.hxx>
41
#include <rtl/math.hxx>
42
#include <tools/date.hxx>
43
#include <tools/time.hxx>
44
#include <tools/duration.hxx>
45
#include <o3tl/string_view.hxx>
46
#include <map>
47
#include <mutex>
48
#include <utility>
49
50
const sal_uInt16 nCurrFormatDefault = 0;
51
52
using namespace ::com::sun::star;
53
using namespace ::com::sun::star::i18n;
54
using namespace ::com::sun::star::uno;
55
56
namespace
57
{
58
    uno::Sequence< lang::Locale > gInstalledLocales;
59
    std::vector< LanguageType > gInstalledLanguageTypes;
60
}
61
62
sal_uInt8 LocaleDataWrapper::nLocaleDataChecking = 0;
63
64
/**
65
 * Loading LocaleDataWrapper can become expensive because of all the function-symbol lookups required, so
66
 * we cache these.
67
 */
68
// static
69
const LocaleDataWrapper* LocaleDataWrapper::get(const LanguageTag& aLanguageTag)
70
381k
{
71
381k
    static std::map<LanguageTag, std::unique_ptr<LocaleDataWrapper>> gCache;
72
381k
    static std::mutex gMutex;
73
74
381k
    std::unique_lock l(gMutex);
75
381k
    auto it = gCache.find(aLanguageTag);
76
381k
    if (it != gCache.end())
77
381k
        return it->second.get();
78
821
    auto pNew = new LocaleDataWrapper(comphelper::getProcessComponentContext(), aLanguageTag);
79
821
    gCache.insert({aLanguageTag, std::unique_ptr<LocaleDataWrapper>(pNew)});
80
821
    return pNew;
81
381k
};
82
83
84
LocaleDataWrapper::LocaleDataWrapper(
85
            const Reference< uno::XComponentContext > & rxContext,
86
            LanguageTag aLanguageTag
87
            )
88
        :
89
835
        m_xContext( rxContext ),
90
835
        xLD( LocaleData2::create(rxContext) ),
91
835
        maLanguageTag(std::move( aLanguageTag ))
92
835
{
93
835
    loadData();
94
835
    loadDateAcceptancePatterns({});
95
835
}
96
97
LocaleDataWrapper::LocaleDataWrapper(
98
            LanguageTag aLanguageTag,
99
            const std::vector<OUString> & rOverrideDateAcceptancePatterns
100
            )
101
        :
102
9.17k
        m_xContext( comphelper::getProcessComponentContext() ),
103
9.17k
        xLD( LocaleData2::create(m_xContext) ),
104
9.17k
        maLanguageTag(std::move( aLanguageTag ))
105
9.17k
{
106
9.17k
    loadData();
107
9.17k
    loadDateAcceptancePatterns(rOverrideDateAcceptancePatterns);
108
9.17k
}
109
110
LocaleDataWrapper::~LocaleDataWrapper()
111
8.80k
{
112
8.80k
}
113
114
const LanguageTag& LocaleDataWrapper::getLanguageTag() const
115
320k
{
116
320k
    return maLanguageTag;
117
320k
}
118
119
const css::lang::Locale& LocaleDataWrapper::getMyLocale() const
120
3.37M
{
121
3.37M
    return maLanguageTag.getLocale();
122
3.37M
}
123
124
void LocaleDataWrapper::loadData()
125
9.92k
{
126
9.92k
    const css::lang::Locale& rMyLocale = maLanguageTag.getLocale();
127
128
9.92k
    {
129
9.92k
        const Sequence< Currency2 > aCurrSeq = getAllCurrencies();
130
9.92k
        if ( !aCurrSeq.hasElements() )
131
0
        {
132
0
            if (areChecksEnabled())
133
0
                outputCheckMessage("LocaleDataWrapper::getCurrSymbolsImpl: no currency at all, using ShellsAndPebbles");
134
0
            aCurrSymbol = "ShellsAndPebbles";
135
0
            aCurrBankSymbol = aCurrSymbol;
136
0
            nCurrPositiveFormat = nCurrNegativeFormat = nCurrFormatDefault;
137
0
            nCurrDigits = 2;
138
0
        }
139
9.92k
        else
140
9.92k
        {
141
9.92k
            auto pCurr = std::find_if(aCurrSeq.begin(), aCurrSeq.end(),
142
9.93k
                [](const Currency2& rCurr) { return rCurr.Default; });
143
9.92k
            if ( pCurr == aCurrSeq.end() )
144
0
            {
145
0
                if (areChecksEnabled())
146
0
                {
147
0
                    outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrSymbolsImpl: no default currency" ) );
148
0
                }
149
0
                pCurr = aCurrSeq.begin();
150
0
            }
151
9.92k
            aCurrSymbol = pCurr->Symbol;
152
9.92k
            aCurrBankSymbol = pCurr->BankSymbol;
153
9.92k
            nCurrDigits = pCurr->DecimalPlaces;
154
9.92k
        }
155
9.92k
    }
156
157
9.92k
    loadCurrencyFormats();
158
159
9.92k
    {
160
9.92k
        xDefaultCalendar.reset();
161
9.92k
        xSecondaryCalendar.reset();
162
9.92k
        const Sequence< Calendar2 > xCals = getAllCalendars();
163
9.92k
        if (xCals.getLength() > 1)
164
8.38k
        {
165
8.38k
            auto pCal = std::find_if(xCals.begin(), xCals.end(),
166
16.7k
                [](const Calendar2& rCal) { return !rCal.Default; });
167
8.38k
            if (pCal != xCals.end())
168
8.38k
                xSecondaryCalendar = std::make_shared<Calendar2>( *pCal);
169
8.38k
        }
170
9.92k
        auto pCal = xCals.begin();
171
9.92k
        if (xCals.getLength() > 1)
172
8.38k
        {
173
8.38k
            pCal = std::find_if(xCals.begin(), xCals.end(),
174
8.38k
                [](const Calendar2& rCal) { return rCal.Default; });
175
8.38k
            if (pCal == xCals.end())
176
0
                pCal = xCals.begin();
177
8.38k
        }
178
9.92k
        xDefaultCalendar = std::make_shared<Calendar2>( *pCal);
179
9.92k
    }
180
181
9.92k
    loadDateOrders();
182
183
9.92k
    try
184
9.92k
    {
185
9.92k
        aDateAcceptancePatterns = xLD->getDateAcceptancePatterns( rMyLocale );
186
9.92k
    }
187
9.92k
    catch (const Exception&)
188
9.92k
    {
189
0
        TOOLS_WARN_EXCEPTION( "unotools.i18n", "getDateAcceptancePatterns" );
190
0
        aDateAcceptancePatterns = {};
191
0
    }
192
193
194
8.83k
    loadDigitGrouping();
195
196
8.83k
    try
197
8.83k
    {
198
8.83k
        aReservedWords = comphelper::sequenceToContainer<std::vector<OUString>>(xLD->getReservedWord( rMyLocale ));
199
8.83k
    }
200
8.83k
    catch ( const Exception& )
201
8.83k
    {
202
0
        TOOLS_WARN_EXCEPTION( "unotools.i18n", "getReservedWord" );
203
0
    }
204
205
8.83k
    try
206
8.83k
    {
207
8.83k
        aLocaleDataItem = xLD->getLocaleItem2( rMyLocale );
208
8.83k
    }
209
8.83k
    catch (const Exception&)
210
8.83k
    {
211
0
        TOOLS_WARN_EXCEPTION( "unotools.i18n", "getLocaleItem" );
212
0
        static const css::i18n::LocaleDataItem2 aEmptyItem;
213
0
        aLocaleDataItem = aEmptyItem;
214
0
    }
215
216
8.83k
    aLocaleItem[LocaleItem::DATE_SEPARATOR] = aLocaleDataItem.dateSeparator;
217
8.83k
    aLocaleItem[LocaleItem::THOUSAND_SEPARATOR] = aLocaleDataItem.thousandSeparator;
218
8.83k
    aLocaleItem[LocaleItem::DECIMAL_SEPARATOR] = aLocaleDataItem.decimalSeparator;
219
8.83k
    aLocaleItem[LocaleItem::TIME_SEPARATOR] = aLocaleDataItem.timeSeparator;
220
8.83k
    aLocaleItem[LocaleItem::TIME_100SEC_SEPARATOR] = aLocaleDataItem.time100SecSeparator;
221
8.83k
    aLocaleItem[LocaleItem::LIST_SEPARATOR] = aLocaleDataItem.listSeparator;
222
8.83k
    aLocaleItem[LocaleItem::SINGLE_QUOTATION_START] = aLocaleDataItem.quotationStart;
223
8.83k
    aLocaleItem[LocaleItem::SINGLE_QUOTATION_END] = aLocaleDataItem.quotationEnd;
224
8.83k
    aLocaleItem[LocaleItem::DOUBLE_QUOTATION_START] = aLocaleDataItem.doubleQuotationStart;
225
8.83k
    aLocaleItem[LocaleItem::DOUBLE_QUOTATION_END] = aLocaleDataItem.doubleQuotationEnd;
226
8.83k
    aLocaleItem[LocaleItem::MEASUREMENT_SYSTEM] = aLocaleDataItem.measurementSystem;
227
8.83k
    aLocaleItem[LocaleItem::TIME_AM] = aLocaleDataItem.timeAM;
228
8.83k
    aLocaleItem[LocaleItem::TIME_PM] = aLocaleDataItem.timePM;
229
8.83k
    aLocaleItem[LocaleItem::LONG_DATE_DAY_OF_WEEK_SEPARATOR] = aLocaleDataItem.LongDateDayOfWeekSeparator;
230
8.83k
    aLocaleItem[LocaleItem::LONG_DATE_DAY_SEPARATOR] = aLocaleDataItem.LongDateDaySeparator;
231
8.83k
    aLocaleItem[LocaleItem::LONG_DATE_MONTH_SEPARATOR] = aLocaleDataItem.LongDateMonthSeparator;
232
8.83k
    aLocaleItem[LocaleItem::LONG_DATE_YEAR_SEPARATOR] = aLocaleDataItem.LongDateYearSeparator;
233
8.83k
    aLocaleItem[LocaleItem::DECIMAL_SEPARATOR_ALTERNATIVE] = aLocaleDataItem.decimalSeparatorAlternative;
234
8.83k
}
235
236
/* FIXME-BCP47: locale data should provide a language tag instead that could be
237
 * passed on. */
238
css::i18n::LanguageCountryInfo LocaleDataWrapper::getLanguageCountryInfo() const
239
1.66M
{
240
1.66M
    try
241
1.66M
    {
242
1.66M
        return xLD->getLanguageCountryInfo( getMyLocale() );
243
1.66M
    }
244
1.66M
    catch (const Exception&)
245
1.66M
    {
246
0
        TOOLS_WARN_EXCEPTION( "unotools.i18n", "getLanguageCountryInfo" );
247
0
    }
248
0
    return css::i18n::LanguageCountryInfo();
249
1.66M
}
250
251
const css::i18n::LocaleDataItem2& LocaleDataWrapper::getLocaleItem() const
252
0
{
253
0
    return aLocaleDataItem;
254
0
}
255
256
css::uno::Sequence< css::i18n::Currency2 > LocaleDataWrapper::getAllCurrencies() const
257
1.66M
{
258
1.66M
    try
259
1.66M
    {
260
1.66M
        return xLD->getAllCurrencies2( getMyLocale() );
261
1.66M
    }
262
1.66M
    catch (const Exception&)
263
1.66M
    {
264
0
        TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllCurrencies" );
265
0
    }
266
0
    return {};
267
1.66M
}
268
269
css::uno::Sequence< css::i18n::FormatElement > LocaleDataWrapper::getAllFormats() const
270
0
{
271
0
    try
272
0
    {
273
0
        return xLD->getAllFormats( getMyLocale() );
274
0
    }
275
0
    catch (const Exception&)
276
0
    {
277
0
        TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllFormats" );
278
0
    }
279
0
    return {};
280
0
}
281
282
css::i18n::ForbiddenCharacters LocaleDataWrapper::getForbiddenCharacters() const
283
30.5k
{
284
30.5k
    try
285
30.5k
    {
286
30.5k
        return xLD->getForbiddenCharacters( getMyLocale() );
287
30.5k
    }
288
30.5k
    catch (const Exception&)
289
30.5k
    {
290
0
        TOOLS_WARN_EXCEPTION( "unotools.i18n", "getForbiddenCharacters" );
291
0
    }
292
0
    return css::i18n::ForbiddenCharacters();
293
30.5k
}
294
295
const css::uno::Sequence< css::lang::Locale > & LocaleDataWrapper::getAllInstalledLocaleNames() const
296
14
{
297
14
    uno::Sequence< lang::Locale > &rInstalledLocales = gInstalledLocales;
298
299
14
    if ( rInstalledLocales.hasElements() )
300
0
        return rInstalledLocales;
301
302
14
    try
303
14
    {
304
14
        rInstalledLocales = xLD->getAllInstalledLocaleNames();
305
14
    }
306
14
    catch ( const Exception& )
307
14
    {
308
0
        TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllInstalledLocaleNames" );
309
0
    }
310
14
    return rInstalledLocales;
311
14
}
312
313
// --- Impl and helpers ----------------------------------------------------
314
315
// static
316
const css::uno::Sequence< css::lang::Locale >& LocaleDataWrapper::getInstalledLocaleNames()
317
14
{
318
14
    const uno::Sequence< lang::Locale > &rInstalledLocales = gInstalledLocales;
319
320
14
    if ( !rInstalledLocales.hasElements() )
321
14
    {
322
14
        LocaleDataWrapper aLDW( ::comphelper::getProcessComponentContext(), LanguageTag( LANGUAGE_SYSTEM) );
323
14
        aLDW.getAllInstalledLocaleNames();
324
14
    }
325
14
    return rInstalledLocales;
326
14
}
327
328
// static
329
const std::vector< LanguageType >& LocaleDataWrapper::getInstalledLanguageTypes()
330
0
{
331
0
    std::vector< LanguageType > &rInstalledLanguageTypes = gInstalledLanguageTypes;
332
333
0
    if ( !rInstalledLanguageTypes.empty() )
334
0
        return rInstalledLanguageTypes;
335
336
0
    const css::uno::Sequence< css::lang::Locale > xLoc =  getInstalledLocaleNames();
337
0
    sal_Int32 nCount = xLoc.getLength();
338
0
    std::vector< LanguageType > xLang;
339
0
    xLang.reserve(nCount);
340
0
    for ( const auto& rLoc : xLoc )
341
0
    {
342
0
        LanguageTag aLanguageTag( rLoc );
343
0
        OUString aDebugLocale;
344
0
        if (areChecksEnabled())
345
0
        {
346
0
            aDebugLocale = aLanguageTag.getBcp47( false);
347
0
        }
348
349
0
        LanguageType eLang = aLanguageTag.getLanguageType( false);
350
0
        if (areChecksEnabled() && eLang == LANGUAGE_DONTKNOW)
351
0
        {
352
0
            OUString aMsg = "ConvertIsoNamesToLanguage: unknown MS-LCID for locale\n" +
353
0
                aDebugLocale;
354
0
            outputCheckMessage(aMsg);
355
0
        }
356
357
0
        if ( eLang == LANGUAGE_NORWEGIAN)       // no_NO, not Bokmal (nb_NO), not Nynorsk (nn_NO)
358
0
            eLang = LANGUAGE_DONTKNOW;  // don't offer "Unknown" language
359
0
        if ( eLang != LANGUAGE_DONTKNOW )
360
0
        {
361
0
            LanguageTag aBackLanguageTag( eLang);
362
0
            if ( aLanguageTag != aBackLanguageTag )
363
0
            {
364
                // In checks, exclude known problems because no MS-LCID defined
365
                // and default for Language found.
366
0
                if ( areChecksEnabled()
367
0
                        && aDebugLocale != "ar-SD"  // Sudan/ar
368
0
                        && aDebugLocale != "en-CB"  // Caribbean is not a country
369
//                      && aDebugLocale != "en-BG"  // ?!? Bulgaria/en
370
//                      && aDebugLocale != "es-BR"  // ?!? Brazil/es
371
0
                    )
372
0
                {
373
0
                    outputCheckMessage(Concat2View(
374
0
                        "ConvertIsoNamesToLanguage/ConvertLanguageToIsoNames: ambiguous locale (MS-LCID?)\n"
375
0
                        + aDebugLocale
376
0
                        + "  ->  0x"
377
0
                        + OUString::number(static_cast<sal_Int32>(static_cast<sal_uInt16>(eLang)), 16)
378
0
                        + "  ->  "
379
0
                        + aBackLanguageTag.getBcp47() ));
380
0
                }
381
0
                eLang = LANGUAGE_DONTKNOW;
382
0
            }
383
0
        }
384
0
        if ( eLang != LANGUAGE_DONTKNOW )
385
0
            xLang.push_back(eLang);
386
0
    }
387
0
    rInstalledLanguageTypes = std::move(xLang);
388
389
0
    return rInstalledLanguageTypes;
390
0
}
391
392
const OUString& LocaleDataWrapper::getOneLocaleItem( sal_Int16 nItem ) const
393
341M
{
394
341M
    if ( nItem >= LocaleItem::COUNT2 )
395
0
    {
396
0
        SAL_WARN( "unotools.i18n", "getOneLocaleItem: bounds" );
397
0
        return aLocaleItem[0];
398
0
    }
399
341M
    return aLocaleItem[nItem];
400
341M
}
401
402
const OUString& LocaleDataWrapper::getOneReservedWord( sal_Int16 nWord ) const
403
3.34M
{
404
3.34M
    if ( nWord < 0 || o3tl::make_unsigned(nWord) >= aReservedWords.size() )
405
0
    {
406
0
        SAL_WARN( "unotools.i18n", "getOneReservedWord: bounds" );
407
0
        return EMPTY_OUSTRING;
408
0
    }
409
3.34M
    return aReservedWords[nWord];
410
3.34M
}
411
412
// static
413
MeasurementSystem LocaleDataWrapper::mapMeasurementStringToEnum( std::u16string_view rMS )
414
151k
{
415
//! TODO: could be cached too
416
151k
    if ( o3tl::equalsIgnoreAsciiCase( rMS, u"metric" ) )
417
0
        return MeasurementSystem::Metric;
418
//! TODO: other measurement systems? => extend enum MeasurementSystem
419
151k
    return MeasurementSystem::US;
420
151k
}
421
422
bool LocaleDataWrapper::doesSecondaryCalendarUseEC( std::u16string_view rName ) const
423
0
{
424
0
    if (rName.empty())
425
0
        return false;
426
427
    // Check language tag first to avoid loading all calendars of this locale.
428
0
    LanguageTag aLoaded( getLoadedLanguageTag());
429
0
    const OUString& aBcp47( aLoaded.getBcp47());
430
    // So far determine only by locale, we know for a few.
431
    /* TODO: check date format codes? or add to locale data? */
432
0
    if (    aBcp47 != "ja-JP" &&
433
0
            aBcp47 != "lo-LA" &&
434
0
            aBcp47 != "zh-TW")
435
0
        return false;
436
437
0
    if (!xSecondaryCalendar)
438
0
        return false;
439
0
    if (!xSecondaryCalendar->Name.equalsIgnoreAsciiCase( rName))
440
0
        return false;
441
442
0
    return true;
443
0
}
444
445
const std::shared_ptr< css::i18n::Calendar2 >& LocaleDataWrapper::getDefaultCalendar() const
446
0
{
447
0
    return xDefaultCalendar;
448
0
}
449
450
css::uno::Sequence< css::i18n::CalendarItem2 > const & LocaleDataWrapper::getDefaultCalendarDays() const
451
0
{
452
0
    return getDefaultCalendar()->Days;
453
0
}
454
455
css::uno::Sequence< css::i18n::CalendarItem2 > const & LocaleDataWrapper::getDefaultCalendarMonths() const
456
0
{
457
0
    return getDefaultCalendar()->Months;
458
0
}
459
460
// --- currencies -----------------------------------------------------
461
462
const OUString& LocaleDataWrapper::getCurrSymbol() const
463
601
{
464
601
    return aCurrSymbol;
465
601
}
466
467
const OUString& LocaleDataWrapper::getCurrBankSymbol() const
468
1.11M
{
469
1.11M
    return aCurrBankSymbol;
470
1.11M
}
471
472
sal_uInt16 LocaleDataWrapper::getCurrPositiveFormat() const
473
216k
{
474
216k
    return nCurrPositiveFormat;
475
216k
}
476
477
sal_uInt16 LocaleDataWrapper::getCurrNegativeFormat() const
478
216k
{
479
216k
    return nCurrNegativeFormat;
480
216k
}
481
482
sal_uInt16 LocaleDataWrapper::getCurrDigits() const
483
597
{
484
597
    return nCurrDigits;
485
597
}
486
487
void LocaleDataWrapper::scanCurrFormatImpl( std::u16string_view rCode,
488
        sal_Int32 nStart, sal_Int32& nSign, sal_Int32& nPar,
489
        sal_Int32& nNum, sal_Int32& nBlank, sal_Int32& nSym ) const
490
17.6k
{
491
17.6k
    nSign = nPar = nNum = nBlank = nSym = -1;
492
17.6k
    const sal_Unicode* const pStr = rCode.data();
493
17.6k
    const sal_Unicode* const pStop = pStr + rCode.size();
494
17.6k
    const sal_Unicode* p = pStr + nStart;
495
17.6k
    int nInSection = 0;
496
17.6k
    bool bQuote = false;
497
363k
    while ( p < pStop )
498
345k
    {
499
345k
        if ( bQuote )
500
0
        {
501
0
            if ( *p == '"' && *(p-1) != '\\' )
502
0
                bQuote = false;
503
0
        }
504
345k
        else
505
345k
        {
506
345k
            switch ( *p )
507
345k
            {
508
0
                case '"' :
509
0
                    if ( pStr == p || *(p-1) != '\\' )
510
0
                        bQuote = true;
511
0
                break;
512
26.4k
                case '-' :
513
26.4k
                    if (!nInSection && nSign == -1)
514
8.83k
                        nSign = p - pStr;
515
26.4k
                break;
516
0
                case '(' :
517
0
                    if (!nInSection && nPar == -1)
518
0
                        nPar = p - pStr;
519
0
                break;
520
70.9k
                case '0' :
521
123k
                case '#' :
522
123k
                    if (!nInSection && nNum == -1)
523
17.6k
                        nNum = p - pStr;
524
123k
                break;
525
26.4k
                case '[' :
526
26.4k
                    nInSection++;
527
26.4k
                break;
528
26.4k
                case ']' :
529
26.4k
                    if ( nInSection )
530
26.4k
                    {
531
26.4k
                        nInSection--;
532
26.4k
                        if (!nInSection && nBlank == -1
533
26.4k
                          && nSym != -1 && p < pStop-1 && *(p+1) == ' ' )
534
56
                            nBlank = p - pStr + 1;
535
26.4k
                    }
536
26.4k
                break;
537
34.6k
                case '$' :
538
34.6k
                    if (nSym == -1 && nInSection && *(p-1) == '[')
539
17.6k
                    {
540
17.6k
                        nSym = p - pStr + 1;
541
17.6k
                        if (nNum != -1 && *(p-2) == ' ')
542
0
                            nBlank = p - pStr - 2;
543
17.6k
                    }
544
34.6k
                break;
545
8.83k
                case ';' :
546
8.83k
                    if ( !nInSection )
547
8.83k
                        p = pStop;
548
8.83k
                break;
549
98.9k
                default:
550
98.9k
                    if (!nInSection && nSym == -1 && o3tl::starts_with(rCode.substr(static_cast<sal_Int32>(p - pStr)), aCurrSymbol))
551
0
                    {   // currency symbol not surrounded by [$...]
552
0
                        nSym = p - pStr;
553
0
                        if (nBlank == -1 && pStr < p && *(p-1) == ' ')
554
0
                            nBlank = p - pStr - 1;
555
0
                        p += aCurrSymbol.getLength() - 1;
556
0
                        if (nBlank == -1 && p < pStop-2 && *(p+2) == ' ')
557
0
                            nBlank = p - pStr + 2;
558
0
                    }
559
345k
            }
560
345k
        }
561
345k
        p++;
562
345k
    }
563
17.6k
}
564
565
void LocaleDataWrapper::loadCurrencyFormats()
566
9.92k
{
567
9.92k
    css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext );
568
9.92k
    uno::Sequence< NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( KNumberFormatUsage::CURRENCY, maLanguageTag.getLocale() );
569
9.92k
    sal_Int32 nCnt = aFormatSeq.getLength();
570
9.92k
    if ( !nCnt )
571
0
    {   // bad luck
572
0
        if (areChecksEnabled())
573
0
        {
574
0
            outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrFormatsImpl: no currency formats" ) );
575
0
        }
576
0
        nCurrPositiveFormat = nCurrNegativeFormat = nCurrFormatDefault;
577
0
        return;
578
0
    }
579
    // find a negative code (medium preferred) and a default (medium preferred) (not necessarily the same)
580
9.92k
    NumberFormatCode const * const pFormatArr = aFormatSeq.getArray();
581
9.92k
    sal_Int32 nElem, nDef, nNeg, nMedium;
582
9.92k
    nDef = nNeg = nMedium = -1;
583
115k
    for ( nElem = 0; nElem < nCnt; nElem++ )
584
105k
    {
585
105k
        if ( pFormatArr[nElem].Type == KNumberFormatType::MEDIUM )
586
88.1k
        {
587
88.1k
            if ( pFormatArr[nElem].Default )
588
8.83k
            {
589
8.83k
                nDef = nElem;
590
8.83k
                nMedium = nElem;
591
8.83k
                if ( pFormatArr[nElem].Code.indexOf( ';' ) >= 0 )
592
8.83k
                    nNeg = nElem;
593
8.83k
            }
594
79.3k
            else
595
79.3k
            {
596
79.3k
                if ( (nNeg == -1 || nMedium == -1) && pFormatArr[nElem].Code.indexOf( ';' ) >= 0 )
597
8.53k
                    nNeg = nElem;
598
79.3k
                if ( nMedium == -1 )
599
8.83k
                    nMedium = nElem;
600
79.3k
            }
601
88.1k
        }
602
17.5k
        else
603
17.5k
        {
604
17.5k
            if ( nDef == -1 && pFormatArr[nElem].Default )
605
8.83k
                nDef = nElem;
606
17.5k
            if ( nNeg == -1 && pFormatArr[nElem].Code.indexOf( ';' ) >= 0 )
607
8.83k
                nNeg = nElem;
608
17.5k
        }
609
105k
    }
610
611
9.92k
    sal_Int32 nSign, nPar, nNum, nBlank, nSym;
612
613
    // positive format
614
9.92k
    nElem = (nDef >= 0 ? nDef : (nNeg >= 0 ? nNeg : 0));
615
9.92k
    scanCurrFormatImpl( pFormatArr[nElem].Code, 0, nSign, nPar, nNum, nBlank, nSym );
616
9.92k
    if (areChecksEnabled() && (nNum == -1 || nSym == -1))
617
0
    {
618
0
        outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrFormatsImpl: CurrPositiveFormat?" ) );
619
0
    }
620
9.92k
    if (nBlank == -1)
621
8.78k
    {
622
8.78k
        if ( nSym < nNum )
623
8.78k
            nCurrPositiveFormat = 0;    // $1
624
0
        else
625
0
            nCurrPositiveFormat = 1;    // 1$
626
8.78k
    }
627
1.13k
    else
628
1.13k
    {
629
1.13k
        if ( nSym < nNum )
630
42
            nCurrPositiveFormat = 2;    // $ 1
631
1.08k
        else
632
1.08k
            nCurrPositiveFormat = 3;    // 1 $
633
1.13k
    }
634
635
    // negative format
636
9.92k
    if ( nNeg < 0 )
637
0
        nCurrNegativeFormat = nCurrFormatDefault;
638
9.92k
    else
639
9.92k
    {
640
9.92k
        const OUString& rCode = pFormatArr[nNeg].Code;
641
9.92k
        sal_Int32 nDelim = rCode.indexOf(';');
642
9.92k
        scanCurrFormatImpl( rCode, nDelim+1, nSign, nPar, nNum, nBlank, nSym );
643
9.92k
        if (areChecksEnabled() && (nNum == -1 || nSym == -1 || (nPar == -1 && nSign == -1)))
644
0
        {
645
0
            outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrFormatsImpl: CurrNegativeFormat?" ) );
646
0
        }
647
        // NOTE: one of nPar or nSign are allowed to be -1
648
9.92k
        if (nBlank == -1)
649
8.81k
        {
650
8.81k
            if ( nSym < nNum )
651
8.81k
            {
652
8.81k
                if ( -1 < nPar && nPar < nSym )
653
0
                    nCurrNegativeFormat = 0;    // ($1)
654
8.81k
                else if ( -1 < nSign && nSign < nSym )
655
8.78k
                    nCurrNegativeFormat = 1;    // -$1
656
28
                else if ( nNum < nSign )
657
0
                    nCurrNegativeFormat = 3;    // $1-
658
28
                else
659
28
                    nCurrNegativeFormat = 2;    // $-1
660
8.81k
            }
661
0
            else
662
0
            {
663
0
                if ( -1 < nPar && nPar < nNum )
664
0
                    nCurrNegativeFormat = 4;    // (1$)
665
0
                else if ( -1 < nSign && nSign < nNum )
666
0
                    nCurrNegativeFormat = 5;    // -1$
667
0
                else if ( nSym < nSign )
668
0
                    nCurrNegativeFormat = 7;    // 1$-
669
0
                else
670
0
                    nCurrNegativeFormat = 6;    // 1-$
671
0
            }
672
8.81k
        }
673
1.10k
        else
674
1.10k
        {
675
1.10k
            if ( nSym < nNum )
676
14
            {
677
14
                if ( -1 < nPar && nPar < nSym )
678
0
                    nCurrNegativeFormat = 14;   // ($ 1)
679
14
                else if ( -1 < nSign && nSign < nSym )
680
0
                    nCurrNegativeFormat = 9;    // -$ 1
681
14
                else if ( nNum < nSign )
682
0
                    nCurrNegativeFormat = 12;   // $ 1-
683
14
                else
684
14
                    nCurrNegativeFormat = 11;   // $ -1
685
14
            }
686
1.08k
            else
687
1.08k
            {
688
1.08k
                if ( -1 < nPar && nPar < nNum )
689
0
                    nCurrNegativeFormat = 15;   // (1 $)
690
1.08k
                else if ( -1 < nSign && nSign < nNum )
691
0
                    nCurrNegativeFormat = 8;    // -1 $
692
1.08k
                else if ( nSym < nSign )
693
0
                    nCurrNegativeFormat = 10;   // 1 $-
694
1.08k
                else
695
1.08k
                    nCurrNegativeFormat = 13;   // 1- $
696
1.08k
            }
697
1.10k
        }
698
9.92k
    }
699
9.92k
}
700
701
// --- date -----------------------------------------------------------
702
703
DateOrder LocaleDataWrapper::getDateOrder() const
704
31.1M
{
705
31.1M
    return nDateOrder;
706
31.1M
}
707
708
LongDateOrder LocaleDataWrapper::getLongDateOrder() const
709
19.9k
{
710
19.9k
    return nLongDateOrder;
711
19.9k
}
712
713
LongDateOrder LocaleDataWrapper::scanDateOrderImpl( std::u16string_view rCode ) const
714
17.6k
{
715
    // Only some european versions were translated, the ones with different
716
    // keyword combinations are:
717
    // English DMY, German TMJ, Spanish DMA, French JMA, Italian GMA,
718
    // Dutch DMJ, Finnish PKV
719
720
    // default is English keywords for every other language
721
17.6k
    size_t nDay = rCode.find('D');
722
17.6k
    size_t nMonth = rCode.find('M');
723
17.6k
    size_t nYear = rCode.find('Y');
724
17.6k
    if (nDay == std::u16string_view::npos || nMonth == std::u16string_view::npos || nYear == std::u16string_view::npos)
725
0
    {   // This algorithm assumes that all three parts (DMY) are present
726
0
        if (nMonth == std::u16string_view::npos)
727
0
        {   // only Finnish has something else than 'M' for month
728
0
            nMonth = rCode.find('K');
729
0
            if (nMonth != std::u16string_view::npos)
730
0
            {
731
0
                nDay = rCode.find('P');
732
0
                nYear = rCode.find('V');
733
0
            }
734
0
        }
735
0
        else if (nDay == std::u16string_view::npos)
736
0
        {   // We have a month 'M' if we reach this branch.
737
            // Possible languages containing 'M' but no 'D':
738
            // German, French, Italian
739
0
            nDay = rCode.find('T');         // German
740
0
            if (nDay != std::u16string_view::npos)
741
0
                nYear = rCode.find('J');
742
0
            else
743
0
            {
744
0
                nYear = rCode.find('A');    // French, Italian
745
0
                if (nYear != std::u16string_view::npos)
746
0
                {
747
0
                    nDay = rCode.find('J'); // French
748
0
                    if (nDay == std::u16string_view::npos)
749
0
                        nDay = rCode.find('G'); // Italian
750
0
                }
751
0
            }
752
0
        }
753
0
        else
754
0
        {   // We have a month 'M' and a day 'D'.
755
            // Possible languages containing 'D' and 'M' but not 'Y':
756
            // Spanish, Dutch
757
0
            nYear = rCode.find('A');        // Spanish
758
0
            if (nYear == std::u16string_view::npos)
759
0
                nYear = rCode.find('J');    // Dutch
760
0
        }
761
0
        if (nDay == std::u16string_view::npos || nMonth == std::u16string_view::npos || nYear == std::u16string_view::npos)
762
0
        {
763
0
            if (areChecksEnabled())
764
0
            {
765
0
                outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::scanDateOrder: not all DMY present" ) );
766
0
            }
767
0
            if (nDay == std::u16string_view::npos)
768
0
                nDay = rCode.size();
769
0
            if (nMonth == std::u16string_view::npos)
770
0
                nMonth = rCode.size();
771
0
            if (nYear == std::u16string_view::npos)
772
0
                nYear = rCode.size();
773
0
        }
774
0
    }
775
    // compare with <= because each position may equal rCode.getLength()
776
17.6k
    if ( nDay <= nMonth && nMonth <= nYear )
777
748
        return LongDateOrder::DMY;     // also if every position equals rCode.getLength()
778
16.9k
    else if ( nMonth <= nDay && nDay <= nYear )
779
16.8k
        return LongDateOrder::MDY;
780
80
    else if ( nYear <= nMonth && nMonth <= nDay )
781
80
        return LongDateOrder::YMD;
782
0
    else if ( nYear <= nDay && nDay <= nMonth )
783
0
        return LongDateOrder::YDM;
784
0
    else
785
0
    {
786
0
        if (areChecksEnabled())
787
0
        {
788
0
            outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::scanDateOrder: no magic applicable" ) );
789
0
        }
790
0
        return LongDateOrder::DMY;
791
0
    }
792
17.6k
}
793
794
static DateOrder getDateOrderFromLongDateOrder( LongDateOrder eLong )
795
8.83k
{
796
8.83k
    switch (eLong)
797
8.83k
    {
798
80
        case LongDateOrder::YMD:
799
80
            return DateOrder::YMD;
800
0
        break;
801
353
        case LongDateOrder::DMY:
802
353
            return DateOrder::DMY;
803
0
        break;
804
8.39k
        case LongDateOrder::MDY:
805
8.39k
            return DateOrder::MDY;
806
0
        break;
807
0
        case LongDateOrder::YDM:
808
0
        default:
809
0
            assert(!"unhandled LongDateOrder to DateOrder");
810
0
            return DateOrder::DMY;
811
8.83k
    }
812
8.83k
}
813
814
void LocaleDataWrapper::loadDateOrders()
815
8.83k
{
816
8.83k
    css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext );
817
8.83k
    uno::Sequence< NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( KNumberFormatUsage::DATE, maLanguageTag.getLocale() );
818
8.83k
    sal_Int32 nCnt = aFormatSeq.getLength();
819
8.83k
    if ( !nCnt )
820
0
    {   // bad luck
821
0
        if (areChecksEnabled())
822
0
        {
823
0
            outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getDateOrdersImpl: no date formats" ) );
824
0
        }
825
0
        nDateOrder = DateOrder::DMY;
826
0
        nLongDateOrder = LongDateOrder::DMY;
827
0
        return;
828
0
    }
829
    // find the edit (21), a default (medium preferred),
830
    // a medium (default preferred), and a long (default preferred)
831
8.83k
    NumberFormatCode const * const pFormatArr = aFormatSeq.getArray();
832
8.83k
    sal_Int32 nEdit, nDef, nMedium, nLong;
833
8.83k
    nEdit = nDef = nMedium = nLong = -1;
834
282k
    for ( sal_Int32 nElem = 0; nElem < nCnt; nElem++ )
835
273k
    {
836
273k
        if ( nEdit == -1 && pFormatArr[nElem].Index == NumberFormatIndex::DATE_SYS_DDMMYYYY )
837
8.83k
            nEdit = nElem;
838
273k
        if ( nDef == -1 && pFormatArr[nElem].Default )
839
8.83k
            nDef = nElem;
840
273k
        switch ( pFormatArr[nElem].Type )
841
273k
        {
842
132k
            case KNumberFormatType::MEDIUM :
843
132k
            {
844
132k
                if ( pFormatArr[nElem].Default )
845
8.83k
                {
846
8.83k
                    nDef = nElem;
847
8.83k
                    nMedium = nElem;
848
8.83k
                }
849
123k
                else if ( nMedium == -1 )
850
348
                    nMedium = nElem;
851
132k
            }
852
132k
            break;
853
123k
            case KNumberFormatType::LONG :
854
123k
            {
855
123k
                if ( pFormatArr[nElem].Default )
856
8.83k
                    nLong = nElem;
857
114k
                else if ( nLong == -1 )
858
0
                    nLong = nElem;
859
123k
            }
860
123k
            break;
861
273k
        }
862
273k
    }
863
8.83k
    if ( nEdit == -1 )
864
0
    {
865
0
        if (areChecksEnabled())
866
0
        {
867
0
            outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getDateOrdersImpl: no edit" ) );
868
0
        }
869
0
        if ( nDef == -1 )
870
0
        {
871
0
            if (areChecksEnabled())
872
0
            {
873
0
                outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getDateOrdersImpl: no default" ) );
874
0
            }
875
0
            if ( nMedium != -1 )
876
0
                nDef = nMedium;
877
0
            else if ( nLong != -1 )
878
0
                nDef = nLong;
879
0
            else
880
0
                nDef = 0;
881
0
        }
882
0
        nEdit = nDef;
883
0
    }
884
8.83k
    LongDateOrder nDO = scanDateOrderImpl( pFormatArr[nEdit].Code );
885
8.83k
    if ( pFormatArr[nEdit].Type == KNumberFormatType::LONG )
886
0
    {   // normally this is not the case
887
0
        nLongDateOrder = nDO;
888
0
        nDateOrder = getDateOrderFromLongDateOrder(nDO);
889
0
    }
890
8.83k
    else
891
8.83k
    {
892
        // YDM should not occur in a short/medium date (i.e. no locale has
893
        // that) and is nowhere handled.
894
8.83k
        nDateOrder = getDateOrderFromLongDateOrder(nDO);
895
8.83k
        if ( nLong == -1 )
896
0
            nLongDateOrder = nDO;
897
8.83k
        else
898
8.83k
            nLongDateOrder = scanDateOrderImpl( pFormatArr[nLong].Code );
899
8.83k
    }
900
8.83k
}
901
902
// --- digit grouping -------------------------------------------------
903
904
void LocaleDataWrapper::loadDigitGrouping()
905
8.83k
{
906
    /* TODO: This is a very simplified grouping setup that only serves its
907
     * current purpose for Indian locales. A free-form flexible one would
908
     * obtain grouping from locale data where it could be specified using, for
909
     * example, codes like #,### and #,##,### that would generate the integer
910
     * sequence. Needed additional API and a locale data element.
911
     */
912
913
8.83k
    if (aGrouping.hasElements() && aGrouping[0])
914
0
        return;
915
916
8.83k
    i18n::LanguageCountryInfo aLCInfo( getLanguageCountryInfo());
917
8.83k
    if (aLCInfo.Country.equalsIgnoreAsciiCase("IN") || // India
918
8.79k
        aLCInfo.Country.equalsIgnoreAsciiCase("BT") )  // Bhutan
919
34
    {
920
34
        aGrouping = { 3, 2, 0 };
921
34
    }
922
8.79k
    else
923
8.79k
    {
924
8.79k
        aGrouping = { 3, 0, 0 };
925
8.79k
    }
926
8.83k
}
927
928
const css::uno::Sequence< sal_Int32 >& LocaleDataWrapper::getDigitGrouping() const
929
9.41M
{
930
9.41M
    return aGrouping;
931
9.41M
}
932
933
// --- simple number formatting helpers -------------------------------
934
935
// The ImplAdd... methods are taken from class International and modified to
936
// suit the needs.
937
938
static void ImplAddUNum( OUStringBuffer& rBuf, sal_uInt64 nNumber )
939
0
{
940
    // fill temp buffer with digits
941
0
    sal_Unicode aTempBuf[64];
942
0
    sal_Unicode* pTempBuf = aTempBuf;
943
0
    do
944
0
    {
945
0
        *pTempBuf = static_cast<sal_Unicode>(nNumber % 10) + '0';
946
0
        pTempBuf++;
947
0
        nNumber /= 10;
948
0
    }
949
0
    while ( nNumber );
950
951
    // copy temp buffer to buffer passed
952
0
    do
953
0
    {
954
0
        pTempBuf--;
955
0
        rBuf.append(*pTempBuf);
956
0
    }
957
0
    while ( pTempBuf != aTempBuf );
958
0
}
959
960
static void ImplAddUNum( OUStringBuffer& rBuf, sal_uInt64 nNumber, int nMinLen )
961
717k
{
962
    // fill temp buffer with digits
963
717k
    sal_Unicode aTempBuf[64];
964
717k
    sal_Unicode* pTempBuf = aTempBuf;
965
717k
    do
966
871k
    {
967
871k
        *pTempBuf = static_cast<sal_Unicode>(nNumber % 10) + '0';
968
871k
        pTempBuf++;
969
871k
        nNumber /= 10;
970
871k
        nMinLen--;
971
871k
    }
972
871k
    while ( nNumber );
973
974
    // fill with zeros up to the minimal length
975
2.71M
    while ( nMinLen > 0 )
976
1.99M
    {
977
1.99M
        rBuf.append('0');
978
1.99M
        nMinLen--;
979
1.99M
    }
980
981
    // copy temp buffer to real buffer
982
717k
    do
983
871k
    {
984
871k
        pTempBuf--;
985
871k
        rBuf.append(*pTempBuf);
986
871k
    }
987
871k
    while ( pTempBuf != aTempBuf );
988
717k
}
989
990
static void ImplAddNum( OUStringBuffer& rBuf, sal_Int64 nNumber, int nMinLen )
991
717k
{
992
717k
    if (nNumber < 0)
993
0
    {
994
0
        rBuf.append('-');
995
0
        nNumber = -nNumber;
996
0
    }
997
717k
    return ImplAddUNum( rBuf, nNumber, nMinLen);
998
717k
}
999
1000
static void ImplAdd2UNum( OUStringBuffer& rBuf, sal_uInt16 nNumber )
1001
5.04M
{
1002
5.04M
    DBG_ASSERT( nNumber < 100, "ImplAdd2UNum() - Number >= 100" );
1003
1004
5.04M
    if ( nNumber < 10 )
1005
4.83M
    {
1006
4.83M
        rBuf.append('0');
1007
4.83M
        rBuf.append(static_cast<char>(nNumber + '0'));
1008
4.83M
    }
1009
202k
    else
1010
202k
    {
1011
202k
        sal_uInt16 nTemp = nNumber % 10;
1012
202k
        nNumber /= 10;
1013
202k
        rBuf.append(static_cast<char>(nNumber + '0'));
1014
202k
        rBuf.append(static_cast<char>(nTemp + '0'));
1015
202k
    }
1016
5.04M
}
1017
1018
static void ImplAdd9UNum( OUStringBuffer& rBuf, sal_uInt32 nNumber )
1019
0
{
1020
0
    DBG_ASSERT( nNumber < 1000000000, "ImplAdd9UNum() - Number >= 1000000000" );
1021
1022
0
    std::ostringstream ostr;
1023
0
    ostr.fill('0');
1024
0
    ostr.width(9);
1025
0
    ostr << nNumber;
1026
0
    std::string aStr = ostr.str();
1027
0
    rBuf.appendAscii(aStr.c_str(), aStr.size());
1028
0
}
1029
1030
void LocaleDataWrapper::ImplAddFormatNum( OUStringBuffer& rBuf,
1031
        sal_Int64 nNumber, sal_uInt16 nDecimals, bool bUseThousandSep,
1032
        bool bTrailingZeros ) const
1033
0
{
1034
0
    OUStringBuffer aNumBuf(64);
1035
0
    sal_uInt16  nNumLen;
1036
1037
    // negative number
1038
0
    sal_uInt64 abs;
1039
0
    if ( nNumber < 0 )
1040
0
    {
1041
        // Avoid overflow, map -2^63 -> 2^63 explicitly:
1042
0
        abs = nNumber == std::numeric_limits<sal_Int64>::min()
1043
0
            ? static_cast<sal_uInt64>(std::numeric_limits<sal_Int64>::min()) : nNumber * -1;
1044
0
        rBuf.append('-');
1045
0
    }
1046
0
    else
1047
0
    {
1048
0
        abs = nNumber;
1049
0
    }
1050
1051
    // convert number
1052
0
    ImplAddUNum( aNumBuf, abs );
1053
0
    nNumLen = static_cast<sal_uInt16>(aNumBuf.getLength());
1054
1055
0
    if ( nNumLen <= nDecimals )
1056
0
    {
1057
        // strip .0 in decimals?
1058
0
        if ( !nNumber && !bTrailingZeros )
1059
0
        {
1060
0
            rBuf.append('0');
1061
0
        }
1062
0
        else
1063
0
        {
1064
            // LeadingZero, insert 0
1065
0
            if ( isNumLeadingZero() )
1066
0
            {
1067
0
                rBuf.append('0');
1068
0
            }
1069
1070
            // append decimal separator
1071
0
            rBuf.append( aLocaleDataItem.decimalSeparator );
1072
1073
            // fill with zeros
1074
0
            sal_uInt16 i = 0;
1075
0
            while ( i < (nDecimals-nNumLen) )
1076
0
            {
1077
0
                rBuf.append('0');
1078
0
                i++;
1079
0
            }
1080
1081
            // append decimals
1082
0
            rBuf.append(aNumBuf);
1083
0
        }
1084
0
    }
1085
0
    else
1086
0
    {
1087
0
        const OUString& rThoSep = aLocaleDataItem.thousandSeparator;
1088
1089
        // copy number to buffer (excluding decimals)
1090
0
        sal_uInt16 nNumLen2 = nNumLen-nDecimals;
1091
0
        uno::Sequence< sal_Bool > aGroupPos;
1092
0
        if (bUseThousandSep)
1093
0
            aGroupPos = utl::DigitGroupingIterator::createForwardSequence(
1094
0
                    nNumLen2, getDigitGrouping());
1095
0
        sal_uInt16 i = 0;
1096
0
        for (; i < nNumLen2; ++i )
1097
0
        {
1098
0
            rBuf.append(aNumBuf[i]);
1099
1100
            // add thousand separator?
1101
0
            if ( bUseThousandSep && aGroupPos[i] )
1102
0
                rBuf.append( rThoSep );
1103
0
        }
1104
1105
        // append decimals
1106
0
        if ( nDecimals )
1107
0
        {
1108
0
            rBuf.append( aLocaleDataItem.decimalSeparator );
1109
1110
0
            bool bNullEnd = true;
1111
0
            while ( i < nNumLen )
1112
0
            {
1113
0
                if ( aNumBuf[i] != '0' )
1114
0
                    bNullEnd = false;
1115
1116
0
                rBuf.append(aNumBuf[i]);
1117
0
                i++;
1118
0
            }
1119
1120
            // strip .0 in decimals?
1121
0
            if ( bNullEnd && !bTrailingZeros )
1122
0
                rBuf.setLength( rBuf.getLength() - (nDecimals + 1) );
1123
0
        }
1124
0
    }
1125
0
}
1126
1127
// --- simple date and time formatting --------------------------------
1128
1129
OUString LocaleDataWrapper::getDate( const Date& rDate ) const
1130
717k
{
1131
//!TODO: leading zeros et al
1132
717k
    OUStringBuffer aBuf(128);
1133
717k
    sal_uInt16  nDay    = rDate.GetDay();
1134
717k
    sal_uInt16  nMonth  = rDate.GetMonth();
1135
717k
    sal_Int16   nYear   = rDate.GetYear();
1136
717k
    sal_uInt16  nYearLen;
1137
1138
717k
    if ( (true) /* IsDateCentury() */ )
1139
717k
        nYearLen = 4;
1140
0
    else
1141
0
    {
1142
0
        nYearLen = 2;
1143
0
        nYear %= 100;
1144
0
    }
1145
1146
717k
    switch ( getDateOrder() )
1147
717k
    {
1148
0
        case DateOrder::DMY :
1149
0
            ImplAdd2UNum( aBuf, nDay );
1150
0
            aBuf.append( aLocaleDataItem.dateSeparator );
1151
0
            ImplAdd2UNum( aBuf, nMonth );
1152
0
            aBuf.append( aLocaleDataItem.dateSeparator );
1153
0
            ImplAddNum( aBuf, nYear, nYearLen );
1154
0
        break;
1155
717k
        case DateOrder::MDY :
1156
717k
            ImplAdd2UNum( aBuf, nMonth );
1157
717k
            aBuf.append( aLocaleDataItem.dateSeparator );
1158
717k
            ImplAdd2UNum( aBuf, nDay );
1159
717k
            aBuf.append( aLocaleDataItem.dateSeparator );
1160
717k
            ImplAddNum( aBuf, nYear, nYearLen );
1161
717k
        break;
1162
0
        default:
1163
0
            ImplAddNum( aBuf, nYear, nYearLen );
1164
0
            aBuf.append( aLocaleDataItem.dateSeparator );
1165
0
            ImplAdd2UNum( aBuf, nMonth );
1166
0
            aBuf.append( aLocaleDataItem.dateSeparator );
1167
0
            ImplAdd2UNum( aBuf, nDay );
1168
717k
    }
1169
1170
717k
    return aBuf.makeStringAndClear();
1171
717k
}
1172
1173
OUString LocaleDataWrapper::getTime( const tools::Time& rTime, bool bSec, bool b100Sec ) const
1174
1.20M
{
1175
//!TODO: leading zeros et al
1176
1.20M
    OUStringBuffer aBuf(128);
1177
1.20M
    sal_uInt16  nHour = rTime.GetHour();
1178
1179
1.20M
    nHour %= 24;
1180
1181
1.20M
    ImplAdd2UNum( aBuf, nHour );
1182
1.20M
    aBuf.append( aLocaleDataItem.timeSeparator );
1183
1.20M
    ImplAdd2UNum( aBuf, rTime.GetMin() );
1184
1.20M
    if ( bSec )
1185
1.20M
    {
1186
1.20M
        aBuf.append( aLocaleDataItem.timeSeparator );
1187
1.20M
        ImplAdd2UNum( aBuf, rTime.GetSec() );
1188
1189
1.20M
        if ( b100Sec )
1190
0
        {
1191
0
            aBuf.append( aLocaleDataItem.time100SecSeparator );
1192
0
            ImplAdd9UNum( aBuf, rTime.GetNanoSec() );
1193
0
        }
1194
1.20M
    }
1195
1196
1.20M
    return aBuf.makeStringAndClear();
1197
1.20M
}
1198
1199
OUString LocaleDataWrapper::getDuration( const tools::Duration& rDuration, bool bSec, bool b100Sec ) const
1200
0
{
1201
0
    OUStringBuffer aBuf(128);
1202
1203
0
    if ( rDuration.IsNegative() )
1204
0
        aBuf.append(' ');
1205
1206
0
    sal_Int64 nHours = static_cast<sal_Int64>(rDuration.GetDays()) * 24 +
1207
0
        (rDuration.IsNegative() ?
1208
0
         -static_cast<sal_Int64>(rDuration.GetTime().GetHour()) :
1209
0
         rDuration.GetTime().GetHour());
1210
0
    if ( (true) /* IsTimeLeadingZero() */ )
1211
0
        ImplAddNum( aBuf, nHours, 2 );
1212
0
    else
1213
0
        ImplAddNum( aBuf, nHours, 1 );
1214
0
    aBuf.append( aLocaleDataItem.timeSeparator );
1215
0
    ImplAdd2UNum( aBuf, rDuration.GetTime().GetMin() );
1216
0
    if ( bSec )
1217
0
    {
1218
0
        aBuf.append( aLocaleDataItem.timeSeparator );
1219
0
        ImplAdd2UNum( aBuf, rDuration.GetTime().GetSec() );
1220
1221
0
        if ( b100Sec )
1222
0
        {
1223
0
            aBuf.append( aLocaleDataItem.time100SecSeparator );
1224
0
            ImplAdd9UNum( aBuf, rDuration.GetTime().GetNanoSec() );
1225
0
        }
1226
0
    }
1227
1228
0
    return aBuf.makeStringAndClear();
1229
0
}
1230
1231
// --- simple number formatting ---------------------------------------
1232
1233
static size_t ImplGetNumberStringLengthGuess( const css::i18n::LocaleDataItem2& rLocaleDataItem, sal_uInt16 nDecimals )
1234
0
{
1235
    // approximately 3.2 bits per digit
1236
0
    const size_t nDig = ((sizeof(sal_Int64) * 8) / 3) + 1;
1237
    // digits, separators (pessimized for insane "every digit may be grouped"), leading zero, sign
1238
0
    size_t nGuess = ((nDecimals < nDig) ?
1239
0
        (((nDig - nDecimals) * rLocaleDataItem.thousandSeparator.getLength()) + nDig) :
1240
0
        nDecimals) + rLocaleDataItem.decimalSeparator.getLength() + 3;
1241
0
    return nGuess;
1242
0
}
1243
1244
OUString LocaleDataWrapper::getNum( sal_Int64 nNumber, sal_uInt16 nDecimals,
1245
        bool bUseThousandSep, bool bTrailingZeros ) const
1246
0
{
1247
    // check if digits and separators will fit into fixed buffer or allocate
1248
0
    size_t nGuess = ImplGetNumberStringLengthGuess( aLocaleDataItem, nDecimals );
1249
0
    OUStringBuffer aBuf(int(nGuess + 16));
1250
1251
0
    ImplAddFormatNum( aBuf, nNumber, nDecimals,
1252
0
        bUseThousandSep, bTrailingZeros );
1253
1254
0
    return aBuf.makeStringAndClear();
1255
0
}
1256
1257
OUString LocaleDataWrapper::getCurr( sal_Int64 nNumber, sal_uInt16 nDecimals,
1258
        std::u16string_view rCurrencySymbol, bool bUseThousandSep ) const
1259
0
{
1260
0
    sal_Unicode cZeroChar = getCurrZeroChar();
1261
1262
    // check if digits and separators will fit into fixed buffer or allocate
1263
0
    size_t nGuess = ImplGetNumberStringLengthGuess( aLocaleDataItem, nDecimals );
1264
0
    OUStringBuffer aNumBuf(sal_Int32(nGuess + 16));
1265
1266
0
    bool bNeg;
1267
0
    if ( nNumber < 0 )
1268
0
    {
1269
0
        bNeg = true;
1270
0
        nNumber *= -1;
1271
0
    }
1272
0
    else
1273
0
        bNeg = false;
1274
1275
    // convert number
1276
0
    ImplAddFormatNum( aNumBuf, nNumber, nDecimals,
1277
0
        bUseThousandSep, true );
1278
0
    const sal_Int32 nNumLen = aNumBuf.getLength();
1279
1280
    // replace zeros with zero character
1281
0
    if ( (cZeroChar != '0') && nDecimals /* && IsNumTrailingZeros() */ )
1282
0
    {
1283
0
        sal_uInt16  i;
1284
0
        bool    bZero = true;
1285
1286
0
        sal_uInt16 nNumBufIndex = nNumLen-nDecimals;
1287
0
        i = 0;
1288
0
        do
1289
0
        {
1290
0
            if ( aNumBuf[nNumBufIndex] != '0' )
1291
0
            {
1292
0
                bZero = false;
1293
0
                break;
1294
0
            }
1295
1296
0
            nNumBufIndex++;
1297
0
            i++;
1298
0
        }
1299
0
        while ( i < nDecimals );
1300
1301
0
        if ( bZero )
1302
0
        {
1303
0
            nNumBufIndex = nNumLen-nDecimals;
1304
0
            i = 0;
1305
0
            do
1306
0
            {
1307
0
                aNumBuf[nNumBufIndex] = cZeroChar;
1308
0
                nNumBufIndex++;
1309
0
                i++;
1310
0
            }
1311
0
            while ( i < nDecimals );
1312
0
        }
1313
0
    }
1314
1315
0
    OUString aCur;
1316
0
    if ( !bNeg )
1317
0
    {
1318
0
        switch( getCurrPositiveFormat() )
1319
0
        {
1320
0
            case 0:
1321
0
                aCur = rCurrencySymbol + aNumBuf;
1322
0
                break;
1323
0
            case 1:
1324
0
                aCur = aNumBuf + rCurrencySymbol;
1325
0
                break;
1326
0
            case 2:
1327
0
                aCur = OUString::Concat(rCurrencySymbol) + " " + aNumBuf;
1328
0
                break;
1329
0
            case 3:
1330
0
                aCur = aNumBuf + " " + rCurrencySymbol;
1331
0
                break;
1332
0
        }
1333
0
    }
1334
0
    else
1335
0
    {
1336
0
        switch( getCurrNegativeFormat() )
1337
0
        {
1338
0
            case 0:
1339
0
                 aCur = OUString::Concat("(") + rCurrencySymbol + aNumBuf + ")";
1340
0
                break;
1341
0
            case 1:
1342
0
                 aCur = OUString::Concat("-") + rCurrencySymbol + aNumBuf;
1343
0
                break;
1344
0
            case 2:
1345
0
                 aCur = OUString::Concat(rCurrencySymbol) + "-" + aNumBuf;
1346
0
                break;
1347
0
            case 3:
1348
0
                 aCur = rCurrencySymbol + aNumBuf + "-";
1349
0
                break;
1350
0
            case 4:
1351
0
                 aCur = "(" + aNumBuf + rCurrencySymbol + ")";
1352
0
                break;
1353
0
            case 5:
1354
0
                 aCur = "-" + aNumBuf + rCurrencySymbol;
1355
0
                break;
1356
0
            case 6:
1357
0
                 aCur = aNumBuf + "-" + rCurrencySymbol;
1358
0
                break;
1359
0
            case 7:
1360
0
                 aCur = aNumBuf + rCurrencySymbol + "-";
1361
0
                break;
1362
0
            case 8:
1363
0
                 aCur = "-" + aNumBuf + " " + rCurrencySymbol;
1364
0
                break;
1365
0
            case 9:
1366
0
                 aCur = OUString::Concat("-") + rCurrencySymbol + " " + aNumBuf;
1367
0
                break;
1368
0
            case 10:
1369
0
                 aCur = aNumBuf + " " + rCurrencySymbol + "-";
1370
0
                break;
1371
0
            case 11:
1372
0
                 aCur = OUString::Concat(rCurrencySymbol) + " -" + aNumBuf;
1373
0
                break;
1374
0
            case 12:
1375
0
                 aCur = OUString::Concat(rCurrencySymbol) + " " + aNumBuf + "-";
1376
0
                break;
1377
0
            case 13:
1378
0
                 aCur = aNumBuf + "- " + rCurrencySymbol;
1379
0
                break;
1380
0
            case 14:
1381
0
                 aCur = OUString::Concat("(") + rCurrencySymbol + " " + aNumBuf + ")";
1382
0
                break;
1383
0
            case 15:
1384
0
                 aCur = "(" + aNumBuf + " " + rCurrencySymbol + ")";
1385
0
                break;
1386
0
        }
1387
0
    }
1388
1389
0
    return aCur;
1390
0
}
1391
1392
// --- number parsing -------------------------------------------------
1393
1394
double LocaleDataWrapper::stringToDouble( std::u16string_view aString, bool bUseGroupSep,
1395
        rtl_math_ConversionStatus* pStatus, sal_Int32* pParseEnd ) const
1396
0
{
1397
0
    const sal_Unicode* pParseEndChar;
1398
0
    double fValue = stringToDouble(aString.data(), aString.data() + aString.size(), bUseGroupSep, pStatus, &pParseEndChar);
1399
0
    if (pParseEnd)
1400
0
        *pParseEnd = pParseEndChar - aString.data();
1401
0
    return fValue;
1402
0
}
1403
1404
double LocaleDataWrapper::stringToDouble( const sal_Unicode* pBegin, const sal_Unicode* pEnd, bool bUseGroupSep,
1405
        rtl_math_ConversionStatus* pStatus, const sal_Unicode** ppParseEnd ) const
1406
833
{
1407
833
    const sal_Unicode cGroupSep = (bUseGroupSep ? aLocaleDataItem.thousandSeparator[0] : 0);
1408
833
    rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
1409
833
    const sal_Unicode* pParseEnd = nullptr;
1410
833
    double fValue = rtl_math_uStringToDouble( pBegin, pEnd, aLocaleDataItem.decimalSeparator[0], cGroupSep, &eStatus, &pParseEnd);
1411
833
    bool bTryAlt = (pParseEnd < pEnd && !aLocaleDataItem.decimalSeparatorAlternative.isEmpty() &&
1412
0
            *pParseEnd == aLocaleDataItem.decimalSeparatorAlternative.toChar());
1413
    // Try re-parsing with alternative if that was the reason to stop.
1414
833
    if (bTryAlt)
1415
0
        fValue = rtl_math_uStringToDouble( pBegin, pEnd, aLocaleDataItem.decimalSeparatorAlternative.toChar(), cGroupSep, &eStatus, &pParseEnd);
1416
833
    if (pStatus)
1417
833
        *pStatus = eStatus;
1418
833
    if (ppParseEnd)
1419
833
        *ppParseEnd = pParseEnd;
1420
833
    return fValue;
1421
833
}
1422
1423
// --- mixed ----------------------------------------------------------
1424
1425
LanguageTag LocaleDataWrapper::getLoadedLanguageTag() const
1426
1.65M
{
1427
1.65M
    LanguageCountryInfo aLCInfo = getLanguageCountryInfo();
1428
1.65M
    return LanguageTag( lang::Locale( aLCInfo.Language, aLCInfo.Country, aLCInfo.Variant ));
1429
1.65M
}
1430
1431
OUString LocaleDataWrapper::appendLocaleInfo(std::u16string_view rDebugMsg) const
1432
0
{
1433
0
    LanguageTag aLoaded = getLoadedLanguageTag();
1434
0
    return OUString::Concat(rDebugMsg) + "\n" + maLanguageTag.getBcp47() + " requested\n"
1435
0
        + aLoaded.getBcp47() + " loaded";
1436
0
}
1437
1438
// static
1439
void LocaleDataWrapper::outputCheckMessage( std::u16string_view rMsg )
1440
0
{
1441
0
    outputCheckMessage(OUStringToOString(rMsg, RTL_TEXTENCODING_UTF8).getStr());
1442
0
}
1443
1444
// static
1445
void LocaleDataWrapper::outputCheckMessage( const char* pStr )
1446
0
{
1447
0
    fprintf( stderr, "\n%s\n", pStr);
1448
0
    fflush( stderr);
1449
0
    SAL_WARN("unotools.i18n", pStr);
1450
0
}
1451
1452
// static
1453
void LocaleDataWrapper::evaluateLocaleDataChecking()
1454
31
{
1455
    // Using the rtl_Instance template here wouldn't solve all threaded write
1456
    // accesses, since we want to assign the result to the static member
1457
    // variable and would need to dereference the pointer returned and assign
1458
    // the value unguarded. This is the same pattern manually coded.
1459
31
    sal_uInt8 nCheck = nLocaleDataChecking;
1460
31
    if (!nCheck)
1461
31
    {
1462
31
        ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex());
1463
31
        nCheck = nLocaleDataChecking;
1464
31
        if (!nCheck)
1465
31
        {
1466
#ifdef DBG_UTIL
1467
            nCheck = 1;
1468
#else
1469
31
            const char* pEnv = getenv( "OOO_ENABLE_LOCALE_DATA_CHECKS");
1470
31
            if (pEnv && (pEnv[0] == 'Y' || pEnv[0] == 'y' || pEnv[0] == '1'))
1471
0
                nCheck = 1;
1472
31
            else
1473
31
                nCheck = 2;
1474
31
#endif
1475
31
            OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
1476
31
            nLocaleDataChecking = nCheck;
1477
31
        }
1478
31
    }
1479
0
    else {
1480
0
        OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
1481
0
    }
1482
31
}
1483
1484
// --- XLocaleData3 ----------------------------------------------------------
1485
1486
css::uno::Sequence< css::i18n::Calendar2 > LocaleDataWrapper::getAllCalendars() const
1487
8.83k
{
1488
8.83k
    try
1489
8.83k
    {
1490
8.83k
        return xLD->getAllCalendars2( getMyLocale() );
1491
8.83k
    }
1492
8.83k
    catch (const Exception&)
1493
8.83k
    {
1494
0
        TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllCalendars" );
1495
0
    }
1496
0
    return {};
1497
8.83k
}
1498
1499
// --- XLocaleData4 ----------------------------------------------------------
1500
1501
const css::uno::Sequence< OUString > & LocaleDataWrapper::getDateAcceptancePatterns() const
1502
175k
{
1503
175k
    return aDateAcceptancePatterns;
1504
175k
}
1505
1506
// --- Override layer --------------------------------------------------------
1507
1508
void LocaleDataWrapper::loadDateAcceptancePatterns(
1509
        const std::vector<OUString> & rPatterns )
1510
8.83k
{
1511
8.83k
    if (!aDateAcceptancePatterns.hasElements() || rPatterns.empty())
1512
8.83k
    {
1513
8.83k
        try
1514
8.83k
        {
1515
8.83k
            aDateAcceptancePatterns = xLD->getDateAcceptancePatterns( maLanguageTag.getLocale() );
1516
8.83k
        }
1517
8.83k
        catch (const Exception&)
1518
8.83k
        {
1519
0
            TOOLS_WARN_EXCEPTION( "unotools.i18n", "setDateAcceptancePatterns" );
1520
0
        }
1521
8.83k
        if (rPatterns.empty())
1522
8.83k
            return;     // just a reset
1523
0
        if (!aDateAcceptancePatterns.hasElements())
1524
0
        {
1525
0
            aDateAcceptancePatterns = comphelper::containerToSequence(rPatterns);
1526
0
            return;
1527
0
        }
1528
0
    }
1529
1530
    // Earlier versions checked for presence of the full date pattern with
1531
    // aDateAcceptancePatterns[0] == rPatterns[0] and prepended that if not.
1532
    // This lead to confusion if the patterns were intentionally specified
1533
    // without, giving entirely a different DMY order, see tdf#150288.
1534
    // Not checking this and accepting the given patterns as is may result in
1535
    // the user shooting themself in the foot, but we can't have both.
1536
0
    aDateAcceptancePatterns = comphelper::containerToSequence(rPatterns);
1537
0
}
1538
1539
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */