Coverage Report

Created: 2026-09-28 10:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/i18npool/source/nativenumber/nativenumbersupplier.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 <i18nlangtag/languagetag.hxx>
21
#include <i18nlangtag/mslangid.hxx>
22
#include <rtl/ustrbuf.hxx>
23
#include <sal/macros.h>
24
#include <nativenumbersupplier.hxx>
25
#include <localedata.hxx>
26
#include "data/numberchar.h"
27
#include <comphelper/processfactory.hxx>
28
#include <cppuhelper/supportsservice.hxx>
29
#include <o3tl/string_view.hxx>
30
#include <cstddef>
31
#include <map>
32
#include <mutex>
33
#include <memory>
34
#include <string_view>
35
#include <unordered_map>
36
#include <com/sun/star/i18n/CharacterClassification.hpp>
37
#include <com/sun/star/i18n/NativeNumberMode.hpp>
38
#include <com/sun/star/linguistic2/NumberText.hpp>
39
40
using namespace ::com::sun::star::uno;
41
using namespace ::com::sun::star::i18n;
42
using namespace ::com::sun::star::lang;
43
44
namespace {
45
46
struct Number {
47
    sal_Int16 number;
48
    const sal_Unicode *multiplierChar;
49
    sal_Int16 numberFlag;
50
    sal_Int16 exponentCount;
51
    const sal_Int16 *multiplierExponent;
52
};
53
54
}
55
56
1.77k
#define NUMBER_OMIT_ZERO (1 << 0)
57
0
#define NUMBER_OMIT_ONLY_ZERO  (1 << 1)
58
#define NUMBER_OMIT_ONE_1  (1 << 2)
59
#define NUMBER_OMIT_ONE_2  (1 << 3)
60
#define NUMBER_OMIT_ONE_3  (1 << 4)
61
#define NUMBER_OMIT_ONE_4  (1 << 5)
62
#define NUMBER_OMIT_ONE_5  (1 << 6)
63
#define NUMBER_OMIT_ONE_6  (1 << 7)
64
#define NUMBER_OMIT_ONE_7  (1 << 8)
65
#define NUMBER_OMIT_ONE  (NUMBER_OMIT_ONE_1|NUMBER_OMIT_ONE_2|NUMBER_OMIT_ONE_3|NUMBER_OMIT_ONE_4|NUMBER_OMIT_ONE_5|NUMBER_OMIT_ONE_6|NUMBER_OMIT_ONE_7)
66
6.06k
#define NUMBER_OMIT_ONE_CHECK(bit)  (1 << (2 + bit))
67
#define NUMBER_OMIT_ALL ( NUMBER_OMIT_ZERO|NUMBER_OMIT_ONE|NUMBER_OMIT_ONLY_ZERO )
68
#define NUMBER_OMIT_ZERO_ONE ( NUMBER_OMIT_ZERO|NUMBER_OMIT_ONE )
69
#define NUMBER_OMIT_ONE_67 (NUMBER_OMIT_ONE_6|NUMBER_OMIT_ONE_7)
70
#define NUMBER_OMIT_ZERO_ONE_67 ( NUMBER_OMIT_ZERO|NUMBER_OMIT_ONE_67 )
71
72
namespace i18npool {
73
74
namespace {
75
76
std::mutex theNatNumMutex;
77
78
}
79
80
static OUString getHebrewNativeNumberString(const OUString& aNumberString, bool useGeresh);
81
82
static OUString getCyrillicNativeNumberString(const OUString& aNumberString);
83
84
/// @throws RuntimeException
85
static OUString AsciiToNativeChar( const OUString& inStr, sal_Int32 nCount,
86
        Sequence< sal_Int32 >* pOffset, sal_Int16 number )
87
18.0k
{
88
18.0k
    const sal_Unicode *src = inStr.getStr();
89
18.0k
    rtl_uString *newStr = rtl_uString_alloc(nCount);
90
18.0k
    if (pOffset)
91
0
        pOffset->realloc(nCount);
92
18.0k
    auto ppOffset = pOffset ? pOffset->getArray() : nullptr;
93
94
78.9k
    for (sal_Int32 i = 0; i < nCount; i++)
95
60.9k
    {
96
60.9k
        sal_Unicode ch = src[i];
97
60.9k
        if (isNumber(ch))
98
56.7k
            newStr->buffer[i] = NumberChar[number][ ch - NUMBER_ZERO ];
99
4.11k
        else if (i+1 < nCount && isNumber(src[i+1])) {
100
3.59k
            if (i > 0 && isNumber(src[i-1]) && isSeparator(ch))
101
0
                newStr->buffer[i] = SeparatorChar[number] ? SeparatorChar[number] : ch;
102
3.59k
            else
103
3.59k
                newStr->buffer[i] = isDecimal(ch) ? (DecimalChar[number] ? DecimalChar[number] : ch) :
104
3.59k
                    isMinus(ch) ? (MinusChar[number] ? MinusChar[number] : ch) : ch;
105
3.59k
        }
106
520
        else
107
520
            newStr->buffer[i] = ch;
108
60.9k
        if (ppOffset)
109
0
            ppOffset[i] = i;
110
60.9k
    }
111
18.0k
    return OUString(newStr, SAL_NO_ACQUIRE); // take ownership
112
18.0k
}
113
114
static bool AsciiToNative_numberMaker(const sal_Unicode *str, sal_Int32 begin, sal_Int32 len,
115
        sal_Unicode *dst, sal_Int32& count, sal_Int16 multiChar_index, Sequence< sal_Int32 >* pOffset, sal_Int32 startPos,
116
 const Number *number, const sal_Unicode* numberChar)
117
21.8k
{
118
21.8k
    sal_Unicode multiChar = (multiChar_index == -1 ? 0 : number->multiplierChar[multiChar_index]);
119
21.8k
    auto ppOffset = pOffset ? pOffset->getArray() : nullptr;
120
21.8k
    if ( len <= number->multiplierExponent[number->exponentCount-1] ) {
121
15.3k
        if (number->multiplierExponent[number->exponentCount-1] > 1) {
122
324
            bool bNotZero = false;
123
648
            for (const sal_Int32 end = begin+len; begin < end; begin++) {
124
324
                if (bNotZero || str[begin] != NUMBER_ZERO) {
125
324
                    dst[count] = numberChar[str[begin] - NUMBER_ZERO];
126
324
                    if (ppOffset)
127
0
                        ppOffset[count] = begin + startPos;
128
324
                    count++;
129
324
                    bNotZero = true;
130
324
                }
131
324
            }
132
324
            if (bNotZero && multiChar > 0) {
133
0
                dst[count] = multiChar;
134
0
                if (ppOffset)
135
0
                    ppOffset[count] = begin + startPos;
136
0
                count++;
137
0
            }
138
324
            return bNotZero;
139
14.9k
        } else if (str[begin] != NUMBER_ZERO) {
140
13.2k
            if (!(number->numberFlag & (multiChar_index < 0 ? 0 : NUMBER_OMIT_ONE_CHECK(multiChar_index))) || str[begin] != NUMBER_ONE) {
141
7.35k
                dst[count] = numberChar[str[begin] - NUMBER_ZERO];
142
7.35k
                if (ppOffset)
143
0
                    ppOffset[count] = begin + startPos;
144
7.35k
                count++;
145
7.35k
            }
146
13.2k
            if (multiChar > 0) {
147
6.06k
                dst[count] = multiChar;
148
6.06k
                if (ppOffset)
149
0
                    ppOffset[count] = begin + startPos;
150
6.06k
                count++;
151
6.06k
            }
152
13.2k
        } else if (!(number->numberFlag & NUMBER_OMIT_ZERO) && count > 0 && dst[count-1] != numberChar[0]) {
153
0
            dst[count] = numberChar[0];
154
0
            if (ppOffset)
155
0
                ppOffset[count] = begin + startPos;
156
0
            count++;
157
0
        }
158
14.9k
        return str[begin] != NUMBER_ZERO;
159
15.3k
    } else {
160
6.55k
        bool bPrintPower = false;
161
        // sal_Int16 last = 0;
162
46.5k
        for (sal_Int16 i = 1; i <= number->exponentCount; i++) {
163
39.9k
            sal_Int32 tmp = len - (i == number->exponentCount ? 0 : number->multiplierExponent[i]);
164
39.9k
            if (tmp > 0) {
165
13.1k
                bPrintPower |= AsciiToNative_numberMaker(str, begin, tmp, dst, count,
166
13.1k
                        (i == number->exponentCount ? -1 : i), pOffset, startPos, number, numberChar);
167
13.1k
                begin += tmp;
168
13.1k
                len -= tmp;
169
13.1k
            }
170
39.9k
        }
171
6.55k
        if (bPrintPower) {
172
6.55k
            if (count > 0 && number->multiplierExponent[number->exponentCount-1] == 1 &&
173
6.55k
                    dst[count-1] == numberChar[0])
174
0
                count--;
175
6.55k
            if (multiChar > 0) {
176
0
                dst[count] = multiChar;
177
0
                if (ppOffset)
178
0
                    ppOffset[count] = begin + startPos;
179
0
                count++;
180
0
            }
181
6.55k
        }
182
6.55k
        return bPrintPower;
183
6.55k
    }
184
21.8k
}
185
186
/// @throws RuntimeException
187
static OUString AsciiToNative( const OUString& inStr, sal_Int32 nCount,
188
        Sequence< sal_Int32 >* pOffset, const Number* number )
189
8.75k
{
190
8.75k
    OUString aRet;
191
192
8.75k
    sal_Int32 strLen = inStr.getLength();
193
8.75k
    const sal_Unicode *numberChar = NumberChar[number->number];
194
195
8.75k
    if (nCount > strLen)
196
0
        nCount = strLen;
197
198
8.75k
    if (nCount > 0)
199
8.75k
    {
200
8.75k
        const sal_Unicode *str = inStr.getStr();
201
8.75k
        std::unique_ptr<sal_Unicode[]> newStr(new sal_Unicode[nCount * 2 + 1]);
202
8.75k
        std::unique_ptr<sal_Unicode[]> srcStr(new sal_Unicode[nCount + 1]); // for keeping number without comma
203
8.75k
        sal_Int32 i, len = 0, count = 0;
204
205
8.75k
        if (pOffset)
206
0
            pOffset->realloc( nCount * 2 );
207
8.75k
        auto ppOffset = pOffset ? pOffset->getArray() : nullptr;
208
8.75k
        bool bDoDecimal = false;
209
210
32.8k
        for (i = 0; i <= nCount; i++)
211
24.0k
        {
212
24.0k
            if (i < nCount && isNumber(str[i])) {
213
15.3k
                if (bDoDecimal) {
214
0
                    newStr[count] = numberChar[str[i] - NUMBER_ZERO];
215
0
                    if (ppOffset)
216
0
                        ppOffset[count] = i;
217
0
                    count++;
218
0
                }
219
15.3k
                else
220
15.3k
                    srcStr[len++] = str[i];
221
15.3k
            } else {
222
8.75k
                if (len > 0) {
223
8.75k
                    if (i < nCount-1 && isSeparator(str[i]) && isNumber(str[i+1]))
224
0
                        continue; // skip comma inside number string
225
8.75k
                    bool bNotZero = false;
226
8.75k
                    for (sal_Int32 begin = 0, end = len % number->multiplierExponent[0];
227
17.5k
                            end <= len; begin = end, end += number->multiplierExponent[0]) {
228
8.75k
                        if (end == 0) continue;
229
8.75k
                        sal_Int32 _count = count;
230
8.75k
                        bNotZero |= AsciiToNative_numberMaker(srcStr.get(), begin, end - begin, newStr.get(), count,
231
8.75k
                                end == len ? -1 : 0, pOffset, i - len, number, numberChar);
232
8.75k
                        if (count > 0 && number->multiplierExponent[number->exponentCount-1] == 1 &&
233
8.42k
                                newStr[count-1] == numberChar[0])
234
0
                            count--;
235
8.75k
                        if (bNotZero && _count == count && end != len) {
236
0
                            newStr[count] = number->multiplierChar[0];
237
0
                            if (ppOffset)
238
0
                                ppOffset[count] = i - len;
239
0
                            count++;
240
0
                        }
241
8.75k
                    }
242
8.75k
                    if (! bNotZero && ! (number->numberFlag & NUMBER_OMIT_ONLY_ZERO)) {
243
0
                        newStr[count] = numberChar[0];
244
0
                        if (ppOffset)
245
0
                            ppOffset[count] = i - len;
246
0
                        count++;
247
0
                    }
248
8.75k
                    len = 0;
249
8.75k
                }
250
8.75k
                if (i < nCount) {
251
0
                    bDoDecimal = (!bDoDecimal && i < nCount-1 && isDecimal(str[i]) && isNumber(str[i+1]));
252
0
                    if (bDoDecimal)
253
0
                        newStr[count] = (DecimalChar[number->number] ? DecimalChar[number->number] : str[i]);
254
0
                    else if (i < nCount-1 && isMinus(str[i]) && isNumber(str[i+1]))
255
0
                        newStr[count] = (MinusChar[number->number] ? MinusChar[number->number] : str[i]);
256
0
                    else if (i < nCount-1 && isSeparator(str[i]) && isNumber(str[i+1]))
257
0
                        newStr[count] = (SeparatorChar[number->number] ? SeparatorChar[number->number] : str[i]);
258
0
                    else
259
0
                        newStr[count] = str[i];
260
0
                    if (ppOffset)
261
0
                        ppOffset[count] = i;
262
0
                    count++;
263
0
                }
264
8.75k
            }
265
24.0k
        }
266
267
8.75k
        if (pOffset)
268
0
            pOffset->realloc(count);
269
8.75k
        aRet = OUString(newStr.get(), count);
270
8.75k
    }
271
8.75k
    return aRet;
272
8.75k
}
273
274
namespace
275
{
276
void NativeToAscii_numberMaker(sal_Int16 max, sal_Int16 prev, const sal_Unicode *str,
277
        sal_Int32& i, sal_Int32 nCount, sal_Unicode *dst, sal_Int32& count, Sequence< sal_Int32 >* pOffset,
278
        OUString& numberChar, OUString& multiplierChar)
279
0
{
280
0
    auto ppOffset = pOffset ? pOffset->getArray() : nullptr;
281
0
    sal_Int16 curr = 0, num = 0, end = 0, shift = 0;
282
0
    while (++i < nCount) {
283
0
        if ((curr = sal::static_int_cast<sal_Int16>( numberChar.indexOf(str[i]) )) >= 0) {
284
0
            if (num > 0)
285
0
                break;
286
0
            num = curr % 10;
287
0
        } else if ((curr = sal::static_int_cast<sal_Int16>( multiplierChar.indexOf(str[i]) )) >= 0) {
288
0
            curr = MultiplierExponent_7_CJK[curr % ExponentCount_7_CJK];
289
0
            if (prev > curr && num == 0) num = 1; // One may be omitted in informal format
290
0
            shift = end = 0;
291
0
            if (curr >= max)
292
0
                max = curr;
293
0
            else if (curr > prev)
294
0
                shift = max - curr;
295
0
            else
296
0
                end = curr;
297
0
            while (end++ < prev) {
298
0
                dst[count] = NUMBER_ZERO + (end == prev ? num : 0);
299
0
                if (ppOffset)
300
0
                    ppOffset[count] = i;
301
0
                count++;
302
0
            }
303
0
            if (shift) {
304
0
                count -= max;
305
0
                for (const sal_Int32 countEnd = count+shift; count < countEnd; count++) {
306
0
                    dst[count] = dst[count + curr];
307
0
                    if (ppOffset)
308
0
                        ppOffset[count] = ppOffset[count + curr];
309
0
                }
310
0
                max = curr;
311
0
            }
312
0
            NativeToAscii_numberMaker(max, curr, str, i, nCount, dst,
313
0
                    count, pOffset, numberChar, multiplierChar);
314
0
            return;
315
0
        } else
316
0
            break;
317
0
    }
318
0
    while (end++ < prev) {
319
0
        dst[count] = NUMBER_ZERO + (end == prev ? num : 0);
320
0
        if (ppOffset)
321
0
            ppOffset[count] = i - 1;
322
0
        count++;
323
0
    }
324
0
}
325
326
/// @throws RuntimeException
327
OUString NativeToAscii(const OUString& inStr,
328
        sal_Int32 nCount, Sequence< sal_Int32 >* pOffset )
329
436
{
330
436
    OUString aRet;
331
332
436
    sal_Int32 strLen = inStr.getLength();
333
334
436
    if (nCount > strLen)
335
0
        nCount = strLen;
336
337
436
    if (nCount > 0) {
338
436
        const sal_Unicode *str = inStr.getStr();
339
436
        std::unique_ptr<sal_Unicode[]> newStr(new sal_Unicode[nCount * MultiplierExponent_7_CJK[0] + 2]);
340
436
        if (pOffset)
341
0
            pOffset->realloc( nCount * MultiplierExponent_7_CJK[0] + 1 );
342
436
        auto ppOffset = pOffset ? pOffset->getArray() : nullptr;
343
436
        sal_Int32 count = 0, index;
344
436
        sal_Int32 i;
345
346
436
        OUString numberChar, multiplierChar, decimalChar, separatorChar;
347
436
        numberChar = OUString(NumberChar[0], 10*NumberChar_Count);
348
436
        multiplierChar = OUString(MultiplierChar_7_CJK[0], ExponentCount_7_CJK*Multiplier_Count);
349
436
        decimalChar = OUString(DecimalChar, NumberChar_Count);
350
436
        std::u16string_view const minusChar(MinusChar, NumberChar_Count);
351
436
        separatorChar = OUString(
352
436
            reinterpret_cast<sal_Unicode *>(SeparatorChar), NumberChar_Count);
353
354
22.3k
        for ( i = 0; i < nCount; i++) {
355
21.9k
            if ((index = multiplierChar.indexOf(str[i])) >= 0) {
356
0
                if (count == 0 || !isNumber(newStr[count-1])) { // add 1 in front of multiplier
357
0
                    newStr[count] = NUMBER_ONE;
358
0
                    if (ppOffset)
359
0
                        ppOffset[count] = i;
360
0
                    count++;
361
0
                }
362
0
                index = MultiplierExponent_7_CJK[index % ExponentCount_7_CJK];
363
0
                NativeToAscii_numberMaker(
364
0
                        sal::static_int_cast<sal_Int16>( index ), sal::static_int_cast<sal_Int16>( index ),
365
0
                        str, i, nCount, newStr.get(), count, pOffset,
366
0
                        numberChar, multiplierChar);
367
21.9k
            } else {
368
21.9k
                if ((index = numberChar.indexOf(str[i])) >= 0)
369
3.69k
                    newStr[count] = sal::static_int_cast<sal_Unicode>( (index % 10) + NUMBER_ZERO );
370
18.2k
                else if (separatorChar.indexOf(str[i]) >= 0 &&
371
676
                        (i < nCount-1 && (numberChar.indexOf(str[i+1]) >= 0 ||
372
538
                                          multiplierChar.indexOf(str[i+1]) >= 0)))
373
69
                    newStr[count] = SeparatorChar[NumberChar_HalfWidth];
374
18.2k
                else if (decimalChar.indexOf(str[i]) >= 0 &&
375
877
                        (i < nCount-1 && (numberChar.indexOf(str[i+1]) >= 0 ||
376
658
                                          multiplierChar.indexOf(str[i+1]) >= 0)))
377
                    // Only when decimal point is followed by numbers,
378
                    // it will be convert to ASCII decimal point
379
201
                    newStr[count] = DecimalChar[NumberChar_HalfWidth];
380
18.0k
                else if (minusChar.find(str[i]) != std::u16string_view::npos &&
381
883
                        (i < nCount-1 && (numberChar.indexOf(str[i+1]) >= 0 ||
382
614
                                          multiplierChar.indexOf(str[i+1]) >= 0)))
383
                    // Only when minus is followed by numbers,
384
                    // it will be convert to ASCII minus sign
385
125
                    newStr[count] = MinusChar[NumberChar_HalfWidth];
386
17.8k
                else
387
17.8k
                    newStr[count] = str[i];
388
21.9k
                if (ppOffset)
389
0
                    ppOffset[count] = i;
390
21.9k
                count++;
391
21.9k
            }
392
21.9k
        }
393
394
436
        if (pOffset) {
395
0
            pOffset->realloc(count);
396
0
        }
397
436
        aRet = OUString(newStr.get(), count);
398
436
    }
399
436
    return aRet;
400
436
}
401
402
const Number natnum4[4] = {
403
        { NumberChar_Lower_zh, MultiplierChar_6_CJK[Multiplier_Lower_zh], 0,
404
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
405
        { NumberChar_Lower_zh, MultiplierChar_6_CJK[Multiplier_Lower_zh_TW], 0,
406
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
407
        { NumberChar_Modern_ja, MultiplierChar_7_CJK[Multiplier_Modern_ja], NUMBER_OMIT_ZERO_ONE_67,
408
                ExponentCount_7_CJK, MultiplierExponent_7_CJK },
409
        { NumberChar_Lower_ko, MultiplierChar_6_CJK[Multiplier_Lower_ko], NUMBER_OMIT_ZERO,
410
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
411
};
412
413
const Number natnum5[4] = {
414
        { NumberChar_Upper_zh, MultiplierChar_6_CJK[Multiplier_Upper_zh], 0,
415
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
416
        { NumberChar_Upper_zh_TW, MultiplierChar_6_CJK[Multiplier_Upper_zh_TW], 0,
417
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
418
        { NumberChar_Traditional_ja, MultiplierChar_7_CJK[Multiplier_Traditional_ja], NUMBER_OMIT_ZERO_ONE_67,
419
                ExponentCount_7_CJK, MultiplierExponent_7_CJK },
420
        { NumberChar_Upper_ko, MultiplierChar_6_CJK[Multiplier_Upper_ko], 0,
421
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
422
};
423
424
const Number natnum6[4] = {
425
        { NumberChar_FullWidth, MultiplierChar_6_CJK[Multiplier_Lower_zh], 0,
426
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
427
        { NumberChar_FullWidth, MultiplierChar_6_CJK[Multiplier_Lower_zh_TW], 0,
428
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
429
        { NumberChar_FullWidth, MultiplierChar_7_CJK[Multiplier_Modern_ja], NUMBER_OMIT_ZERO_ONE_67,
430
                ExponentCount_7_CJK, MultiplierExponent_7_CJK },
431
        { NumberChar_FullWidth, MultiplierChar_6_CJK[Multiplier_Hangul_ko], NUMBER_OMIT_ZERO,
432
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
433
};
434
435
const Number natnum7[4] = {
436
        { NumberChar_Lower_zh, MultiplierChar_6_CJK[Multiplier_Lower_zh], NUMBER_OMIT_ALL,
437
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
438
        { NumberChar_Lower_zh, MultiplierChar_6_CJK[Multiplier_Lower_zh_TW], NUMBER_OMIT_ALL,
439
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
440
        { NumberChar_Modern_ja, MultiplierChar_2_CJK[Multiplier_Modern_ja], NUMBER_OMIT_ZERO_ONE,
441
                ExponentCount_2_CJK, MultiplierExponent_2_CJK },
442
        { NumberChar_Lower_ko, MultiplierChar_6_CJK[Multiplier_Lower_ko], NUMBER_OMIT_ALL,
443
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
444
};
445
446
const Number natnum8[4] = {
447
        { NumberChar_Upper_zh, MultiplierChar_6_CJK[Multiplier_Upper_zh], NUMBER_OMIT_ALL,
448
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
449
        { NumberChar_Upper_zh_TW, MultiplierChar_6_CJK[Multiplier_Upper_zh_TW], NUMBER_OMIT_ALL,
450
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
451
        { NumberChar_Traditional_ja, MultiplierChar_2_CJK[Multiplier_Traditional_ja], NUMBER_OMIT_ZERO_ONE,
452
                ExponentCount_2_CJK, MultiplierExponent_2_CJK },
453
        { NumberChar_Upper_ko, MultiplierChar_6_CJK[Multiplier_Upper_ko], NUMBER_OMIT_ALL,
454
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
455
};
456
457
const Number natnum10 = { NumberChar_Hangul_ko, MultiplierChar_6_CJK[Multiplier_Hangul_ko], NUMBER_OMIT_ZERO,
458
                ExponentCount_6_CJK, MultiplierExponent_6_CJK };
459
const Number natnum11 = { NumberChar_Hangul_ko, MultiplierChar_6_CJK[Multiplier_Hangul_ko], NUMBER_OMIT_ALL,
460
                ExponentCount_6_CJK, MultiplierExponent_6_CJK };
461
462
//! ATTENTION: Do not change order of elements!
463
//! Append new languages to the end of the list!
464
const char* const natnum1Locales[] = {
465
    "zh_CN",
466
    "zh_TW",
467
    "ja",
468
    "ko",
469
    "he",
470
    "ar",
471
    "th",
472
    "hi",
473
    "or",
474
    "mr",
475
    "bn",
476
    "pa",
477
    "gu",
478
    "ta",
479
    "te",
480
    "kn",
481
    "ml",
482
    "lo",
483
    "bo",
484
    "my",
485
    "km",
486
    "mn",
487
    "ne",
488
    "dz",
489
    "fa",
490
    "cu"
491
};
492
const sal_Int16 nbOfLocale = SAL_N_ELEMENTS(natnum1Locales);
493
494
//! ATTENTION: Do not change order of elements!
495
//! Number and order must match elements of natnum1Locales!
496
const sal_Int16 natnum1[] = {
497
    NumberChar_Lower_zh,
498
    NumberChar_Lower_zh,
499
    NumberChar_Modern_ja,
500
    NumberChar_Lower_ko,
501
    NumberChar_he,
502
    NumberChar_Indic_ar,
503
    NumberChar_th,
504
    NumberChar_hi,
505
    NumberChar_or,
506
    NumberChar_mr,
507
    NumberChar_bn,
508
    NumberChar_pa,
509
    NumberChar_gu,
510
    NumberChar_ta,
511
    NumberChar_te,
512
    NumberChar_kn,
513
    NumberChar_ml,
514
    NumberChar_lo,
515
    NumberChar_bo,
516
    NumberChar_my,
517
    NumberChar_km,
518
    NumberChar_mn,
519
    NumberChar_ne,
520
    NumberChar_dz,
521
    NumberChar_EastIndic_ar,
522
    NumberChar_cu
523
};
524
const sal_Int16 sizeof_natnum1 = SAL_N_ELEMENTS(natnum1);
525
526
//! ATTENTION: Do not change order of elements!
527
//! Order must match first elements of natnum1Locales!
528
const sal_Int16 natnum2[] = {
529
    NumberChar_Upper_zh,
530
    NumberChar_Upper_zh_TW,
531
    NumberChar_Traditional_ja,
532
    NumberChar_Upper_ko,
533
    NumberChar_he
534
};
535
const sal_Int16 sizeof_natnum2 = SAL_N_ELEMENTS(natnum2);
536
537
sal_Int16 getLanguageNumber( const Locale& rLocale)
538
122k
{
539
    // return zh_TW for TW, HK and MO, return zh_CN for other zh locales.
540
122k
    if (rLocale.Language == "zh") return MsLangId::isTraditionalChinese(rLocale) ? 1 : 0;
541
542
1.56M
    for (sal_Int16 i = 2; i < nbOfLocale; i++)
543
1.50M
        if (rLocale.Language.equalsAsciiL(natnum1Locales[i], 2))
544
12.5k
            return i;
545
546
61.5k
    return -1;
547
74.0k
}
548
549
struct Separators
550
{
551
    sal_Unicode DecimalSeparator;
552
    sal_Unicode ThousandSeparator;
553
    Separators(const Locale& rLocale)
554
2
    {
555
2
        LocaleDataItem aLocaleItem = LocaleDataImpl::get()->getLocaleItem(rLocale);
556
2
        DecimalSeparator = aLocaleItem.decimalSeparator.toChar();
557
2
        ThousandSeparator = aLocaleItem.thousandSeparator.toChar();
558
2
    }
559
};
560
561
Separators getLocaleSeparators(const Locale& rLocale, const OUString& rLocStr)
562
108
{
563
    // Guard the static variable below.
564
108
    std::scoped_lock aGuard(theNatNumMutex);
565
    // Maximum a couple hundred of pairs with 4-byte structs - so no need for smart managing
566
108
    static std::unordered_map<OUString, Separators> aLocaleSeparatorsBuf;
567
108
    auto it = aLocaleSeparatorsBuf.find(rLocStr);
568
108
    if (it == aLocaleSeparatorsBuf.end())
569
2
    {
570
2
        it = aLocaleSeparatorsBuf.emplace(rLocStr, Separators(rLocale)).first;
571
2
    }
572
108
    return it->second;
573
108
}
574
575
OUString getNumberText(const Locale& rLocale, const OUString& rNumberString,
576
                       std::u16string_view sNumberTextParams)
577
108
{
578
108
    sal_Int32 i, count = 0;
579
108
    const sal_Int32 len = rNumberString.getLength();
580
108
    const sal_Unicode* src = rNumberString.getStr();
581
582
108
    OUString aLoc = LanguageTag::convertToBcp47(rLocale);
583
108
    Separators aSeparators = getLocaleSeparators(rLocale, aLoc);
584
585
108
    OUStringBuffer sBuf(len);
586
216
    for (i = 0; i < len; i++)
587
108
    {
588
108
        sal_Unicode ch = src[i];
589
108
        if (isNumber(ch) || ch == aSeparators.DecimalSeparator)
590
108
        {
591
108
            ++count;
592
108
            sBuf.append(ch);
593
108
        }
594
0
        else if (ch == aSeparators.ThousandSeparator && count > 0)
595
0
            continue;
596
0
        else if (isMinus(ch) && count == 0)
597
0
            sBuf.append(ch);
598
0
        else
599
0
            break;
600
108
    }
601
602
    // Handle also month and day names for NatNum12 date formatting
603
108
    const OUString aNumberStr = (count == 0) ? rNumberString : sBuf.makeStringAndClear();
604
605
108
    static auto xNumberText
606
108
        = css::linguistic2::NumberText::create(comphelper::getProcessComponentContext());
607
608
    // Guard the static variables below.
609
108
    std::scoped_lock aGuard( theNatNumMutex );
610
611
108
    OUString numbertext_prefix;
612
    // default "cardinal" gets empty prefix
613
108
    if (!sNumberTextParams.empty() && sNumberTextParams != u"cardinal")
614
0
        numbertext_prefix = OUString::Concat(sNumberTextParams) + " ";
615
    // Several hundreds of headings could result typing lags because
616
    // of the continuous update of the multiple number names during typing.
617
    // We fix this by buffering the result of the conversion.
618
108
    static std::unordered_map<OUString, std::map<OUString, OUString>> aBuff;
619
108
    auto& rItems = aBuff[aNumberStr];
620
108
    auto& rItem = rItems[numbertext_prefix + aLoc];
621
108
    if (rItem.isEmpty())
622
0
    {
623
0
        rItem = xNumberText->getNumberText(numbertext_prefix + aNumberStr, rLocale);
624
        // use number at missing number to text conversion
625
0
        if (rItem.isEmpty())
626
0
            rItem = aNumberStr;
627
0
    }
628
108
    OUString sResult = rItem;
629
108
    if (i != 0 && i < len)
630
0
        sResult += rNumberString.subView(i);
631
108
    return sResult;
632
108
}
633
}
634
635
OUString NativeNumberSupplierService::getNativeNumberString(const OUString& aNumberString, const Locale& rLocale,
636
                                                            sal_Int16 nNativeNumberMode,
637
                                                            Sequence<sal_Int32>* pOffset,
638
                                                            std::u16string_view rNativeNumberParams)
639
76.5k
{
640
76.5k
    if (!isValidNatNumImpl(rLocale, nNativeNumberMode))
641
30.5k
        return aNumberString;
642
643
46.0k
    if (nNativeNumberMode == NativeNumberMode::NATNUM12)
644
108
    {
645
        // handle capitalization prefixes "capitalize", "upper", "lower" and "title"
646
647
108
        enum WhichCasing
648
108
        {
649
108
            CAPITALIZE,
650
108
            UPPER,
651
108
            LOWER,
652
108
            TITLE
653
108
        };
654
655
108
        struct CasingEntry
656
108
        {
657
108
            std::u16string_view aLiteral;
658
108
            WhichCasing     eCasing;
659
108
        };
660
661
108
        static const CasingEntry Casings[] =
662
108
        {
663
108
            { std::u16string_view(u"capitalize"), CAPITALIZE },
664
108
            { std::u16string_view(u"upper"), UPPER },
665
108
            { std::u16string_view(u"lower"), LOWER },
666
108
            { std::u16string_view(u"title"), TITLE }
667
108
        };
668
669
108
        std::size_t nStripCase = 0;
670
108
        size_t nCasing;
671
540
        for (nCasing = 0; nCasing < std::size(Casings); ++nCasing)
672
432
        {
673
432
            if (o3tl::starts_with(rNativeNumberParams, Casings[nCasing].aLiteral))
674
0
            {
675
0
                nStripCase = Casings[nCasing].aLiteral.size();
676
0
                break;
677
0
            }
678
432
        }
679
680
108
        if (nStripCase > 0 && (rNativeNumberParams.size() == nStripCase ||
681
0
                    rNativeNumberParams[nStripCase++] == ' '))
682
0
        {
683
0
            OUString aStr = getNumberText(rLocale, aNumberString, rNativeNumberParams.substr(nStripCase));
684
685
0
            if (!xCharClass.is())
686
0
                xCharClass = CharacterClassification::create(comphelper::getProcessComponentContext());
687
688
0
            switch (Casings[nCasing].eCasing)
689
0
            {
690
0
                case CAPITALIZE:
691
0
                    return xCharClass->toTitle(aStr, 0, 1, aLocale) +
692
0
                        (aStr.getLength() > 1 ? aStr.subView(1) : u"");
693
0
                case UPPER:
694
0
                    return xCharClass->toUpper(aStr, 0, aStr.getLength(), aLocale);
695
0
                case LOWER:
696
0
                    return xCharClass->toLower(aStr, 0, aStr.getLength(), aLocale);
697
0
                case TITLE:
698
0
                {
699
0
                    if ( rLocale.Language == "en" )
700
0
                    {
701
                        // title case is common in English, so fix bugs of toTitle():
702
                        // not "One Dollar *And* *Twenty-two* Cents", but
703
                        // "One Dollar *and* *Twenty-Two* Cents".
704
705
                        // Add spaces after hyphens to separate the elements of the
706
                        // hyphenated compound words temporarily, allowing their
707
                        // capitalization by toTitle()
708
0
                        aStr = aStr.replaceAll("-", "- ");
709
0
                        aStr = xCharClass->toTitle(aStr, 0, aStr.getLength(), aLocale);
710
0
                        return aStr.replaceAll("- ", "-").replaceAll(" And ", " and ");
711
0
                    }
712
0
                    else
713
0
                        return xCharClass->toTitle(aStr, 0, aStr.getLength(), aLocale);
714
0
               }
715
0
            }
716
0
        }
717
108
        else
718
108
        {
719
108
            return getNumberText(rLocale, aNumberString, rNativeNumberParams);
720
108
        }
721
108
    }
722
723
45.9k
    sal_Int16 langnum = getLanguageNumber(rLocale);
724
45.9k
    if (langnum == -1)
725
15.6k
        return aNumberString;
726
727
30.2k
    const Number *number = nullptr;
728
30.2k
    sal_Int16 num = -1;
729
730
30.2k
    switch (nNativeNumberMode)
731
30.2k
    {
732
436
        case NativeNumberMode::NATNUM0: // Ascii
733
436
            return NativeToAscii(aNumberString, aNumberString.getLength(), pOffset);
734
873
        case NativeNumberMode::NATNUM1: // Char, Lower
735
873
            num = natnum1[langnum];
736
873
            break;
737
19.8k
        case NativeNumberMode::NATNUM2: // Char, Upper
738
19.8k
            num = natnum2[langnum];
739
19.8k
            break;
740
3
        case NativeNumberMode::NATNUM3: // Char, FullWidth
741
3
            num = NumberChar_FullWidth;
742
3
            break;
743
232
        case NativeNumberMode::NATNUM4: // Text, Lower, Long
744
232
            number = &natnum4[langnum];
745
232
            break;
746
1.12k
        case NativeNumberMode::NATNUM5: // Text, Upper, Long
747
1.12k
            number = &natnum5[langnum];
748
1.12k
            break;
749
0
        case NativeNumberMode::NATNUM6: // Text, FullWidth
750
0
            number = &natnum6[langnum];
751
0
            break;
752
0
        case NativeNumberMode::NATNUM7: // Text. Lower, Short
753
0
            number = &natnum7[langnum];
754
0
            break;
755
7.07k
        case NativeNumberMode::NATNUM8: // Text, Upper, Short
756
7.07k
            number = &natnum8[langnum];
757
7.07k
            break;
758
324
        case NativeNumberMode::NATNUM9: // Char, Hangul
759
324
            num = NumberChar_Hangul_ko;
760
324
            break;
761
0
        case NativeNumberMode::NATNUM10:        // Text, Hangul, Long
762
0
            number = &natnum10;
763
0
            break;
764
324
        case NativeNumberMode::NATNUM11:        // Text, Hangul, Short
765
324
            number = &natnum11;
766
324
            break;
767
0
        default:
768
0
            break;
769
30.2k
    }
770
771
29.8k
    if (number || num >= 0) {
772
29.8k
        if (aLocale.Language != rLocale.Language ||
773
28.8k
                aLocale.Country != rLocale.Country ||
774
28.7k
                aLocale.Variant != rLocale.Variant) {
775
1.07k
            LocaleDataItem item = LocaleDataImpl::get()->getLocaleItem( rLocale );
776
1.07k
            aLocale = rLocale;
777
1.07k
            DecimalChar[NumberChar_HalfWidth]=item.decimalSeparator.toChar();
778
1.07k
            if (DecimalChar[NumberChar_HalfWidth] > 0x7E || DecimalChar[NumberChar_HalfWidth] < 0x21)
779
0
                DecimalChar[NumberChar_FullWidth]=0xFF0E;
780
1.07k
            else
781
1.07k
                DecimalChar[NumberChar_FullWidth]=DecimalChar[NumberChar_HalfWidth]+0xFEE0;
782
1.07k
            SeparatorChar[NumberChar_HalfWidth]=item.thousandSeparator.toChar();
783
1.07k
            if (SeparatorChar[NumberChar_HalfWidth] > 0x7E || SeparatorChar[NumberChar_HalfWidth] < 0x21)
784
0
                SeparatorChar[NumberChar_FullWidth]=0xFF0C;
785
1.07k
            else
786
1.07k
                SeparatorChar[NumberChar_FullWidth]=SeparatorChar[NumberChar_HalfWidth]+0xFEE0;
787
1.07k
        }
788
29.8k
        if (number)
789
8.75k
            return AsciiToNative( aNumberString, aNumberString.getLength(), pOffset, number );
790
21.0k
        else if (num == NumberChar_he)
791
3.00k
            return getHebrewNativeNumberString(aNumberString,
792
3.00k
                    nNativeNumberMode == NativeNumberMode::NATNUM2);
793
18.0k
        else if (num == NumberChar_cu)
794
0
            return getCyrillicNativeNumberString(aNumberString);
795
18.0k
        else
796
18.0k
            return AsciiToNativeChar(aNumberString, aNumberString.getLength(), pOffset, num);
797
29.8k
    }
798
0
    else
799
0
        return aNumberString;
800
29.8k
}
801
802
OUString SAL_CALL NativeNumberSupplierService::getNativeNumberString(const OUString& aNumberString, const Locale& rLocale,
803
                sal_Int16 nNativeNumberMode)
804
72.5k
{
805
72.5k
    return getNativeNumberString(aNumberString, rLocale, nNativeNumberMode, nullptr);
806
72.5k
}
807
808
OUString SAL_CALL NativeNumberSupplierService::getNativeNumberStringParams(
809
    const OUString& rNumberString, const css::lang::Locale& rLocale, sal_Int16 nNativeNumberMode,
810
    const OUString& rNativeNumberParams)
811
3.98k
{
812
3.98k
    return getNativeNumberString(rNumberString, rLocale, nNativeNumberMode, nullptr, rNativeNumberParams);
813
3.98k
}
814
815
sal_Unicode NativeNumberSupplierService::getNativeNumberChar( const sal_Unicode inChar, const Locale& rLocale, sal_Int16 nNativeNumberMode )
816
0
{
817
0
    if (nNativeNumberMode == NativeNumberMode::NATNUM0) { // Ascii
818
0
        for (const auto & i : NumberChar)
819
0
            for (sal_Int16 j = 0; j < 10; j++)
820
0
                if (inChar == i[j])
821
0
                    return j;
822
0
        return inChar;
823
0
    }
824
825
0
    if (!isNumber(inChar))
826
0
        return inChar;
827
828
0
    if (!isValidNatNumImpl(rLocale, nNativeNumberMode))
829
0
        return inChar;
830
831
0
    sal_Int16 langnum = getLanguageNumber(rLocale);
832
0
    if (langnum == -1)
833
0
        return inChar;
834
835
0
    switch (nNativeNumberMode)
836
0
    {
837
0
        case NativeNumberMode::NATNUM1: // Char, Lower
838
0
        case NativeNumberMode::NATNUM4: // Text, Lower, Long
839
0
        case NativeNumberMode::NATNUM7: // Text. Lower, Short
840
0
            return NumberChar[natnum1[langnum]][inChar - NUMBER_ZERO];
841
0
        case NativeNumberMode::NATNUM2: // Char, Upper
842
0
        case NativeNumberMode::NATNUM5: // Text, Upper, Long
843
0
        case NativeNumberMode::NATNUM8: // Text, Upper, Short
844
0
            return NumberChar[natnum2[langnum]][inChar - NUMBER_ZERO];
845
0
        case NativeNumberMode::NATNUM3: // Char, FullWidth
846
0
        case NativeNumberMode::NATNUM6: // Text, FullWidth
847
0
            return NumberChar[NumberChar_FullWidth][inChar - NUMBER_ZERO];
848
0
        case NativeNumberMode::NATNUM9: // Char, Hangul
849
0
        case NativeNumberMode::NATNUM10:        // Text, Hangul, Long
850
0
        case NativeNumberMode::NATNUM11:        // Text, Hangul, Short
851
0
            return NumberChar[NumberChar_Hangul_ko][inChar - NUMBER_ZERO];
852
0
        default:
853
0
            break;
854
0
    }
855
856
0
    return inChar;
857
0
}
858
859
bool NativeNumberSupplierService::isValidNatNumImpl( const Locale& rLocale, sal_Int16 nNativeNumberMode )
860
76.5k
{
861
76.5k
    sal_Int16 langnum = getLanguageNumber(rLocale);
862
863
76.5k
    switch (nNativeNumberMode) {
864
15.7k
        case NativeNumberMode::NATNUM0:     // Ascii
865
16.0k
        case NativeNumberMode::NATNUM3:     // Char, FullWidth
866
16.1k
        case NativeNumberMode::NATNUM12:    // spell out numbers, dates and money amounts
867
16.1k
            return true;
868
1.56k
        case NativeNumberMode::NATNUM1:     // Char, Lower
869
1.56k
            return (langnum >= 0);
870
49.7k
        case NativeNumberMode::NATNUM2:     // Char, Upper
871
49.7k
            if (langnum == 4) // Hebrew numbering
872
2.99k
                return true;
873
46.7k
            [[fallthrough]];
874
46.9k
        case NativeNumberMode::NATNUM4:     // Text, Lower, Long
875
48.0k
        case NativeNumberMode::NATNUM5:     // Text, Upper, Long
876
48.0k
        case NativeNumberMode::NATNUM6:     // Text, FullWidth
877
48.0k
        case NativeNumberMode::NATNUM7:     // Text. Lower, Short
878
55.1k
        case NativeNumberMode::NATNUM8:     // Text, Upper, Short
879
55.1k
            return (langnum >= 0 && langnum < 4); // CJK numbering
880
324
        case NativeNumberMode::NATNUM9:     // Char, Hangul
881
324
        case NativeNumberMode::NATNUM10:    // Text, Hangul, Long
882
648
        case NativeNumberMode::NATNUM11:    // Text, Hangul, Short
883
648
            return (langnum == 3); // Korean numbering
884
76.5k
    }
885
0
    return false;
886
76.5k
}
887
888
NativeNumberXmlAttributes SAL_CALL NativeNumberSupplierService::convertToXmlAttributes( const Locale& rLocale, sal_Int16 nNativeNumberMode )
889
0
{
890
0
    static const sal_Int16 attShort         = 0;
891
0
    static const sal_Int16 attMedium        = 1;
892
0
    static const sal_Int16 attLong          = 2;
893
0
    static const char * const attType[] = { "short", "medium", "long" };
894
895
0
    sal_Int16 number = NumberChar_HalfWidth, type = attShort;
896
897
0
    sal_Int16 langnum = -1;
898
0
    if (isValidNatNum(rLocale, nNativeNumberMode)) {
899
0
        langnum = getLanguageNumber(rLocale);
900
0
    }
901
0
    if (langnum != -1) {
902
0
        switch (nNativeNumberMode) {
903
0
            case NativeNumberMode::NATNUM0: // Ascii
904
0
                number = NumberChar_HalfWidth;
905
0
                type = attShort;
906
0
                break;
907
0
            case NativeNumberMode::NATNUM1: // Char, Lower
908
0
                number = natnum1[langnum];
909
0
                type = attShort;
910
0
                break;
911
0
            case NativeNumberMode::NATNUM2: // Char, Upper
912
0
                number = natnum2[langnum];
913
0
                type = number == NumberChar_he ? attMedium : attShort;
914
0
                break;
915
0
            case NativeNumberMode::NATNUM3: // Char, FullWidth
916
0
                number = NumberChar_FullWidth;
917
0
                type = attShort;
918
0
                break;
919
0
            case NativeNumberMode::NATNUM4: // Text, Lower, Long
920
0
                number = natnum1[langnum];
921
0
                type = attLong;
922
0
                break;
923
0
            case NativeNumberMode::NATNUM5: // Text, Upper, Long
924
0
                number = natnum2[langnum];
925
0
                type = attLong;
926
0
                break;
927
0
            case NativeNumberMode::NATNUM6: // Text, FullWidth
928
0
                number = NumberChar_FullWidth;
929
0
                type = attLong;
930
0
                break;
931
0
            case NativeNumberMode::NATNUM7: // Text. Lower, Short
932
0
                number = natnum1[langnum];
933
0
                type = attMedium;
934
0
                break;
935
0
            case NativeNumberMode::NATNUM8: // Text, Upper, Short
936
0
                number = natnum2[langnum];
937
0
                type = attMedium;
938
0
                break;
939
0
            case NativeNumberMode::NATNUM9: // Char, Hangul
940
0
                number = NumberChar_Hangul_ko;
941
0
                type = attShort;
942
0
                break;
943
0
            case NativeNumberMode::NATNUM10:        // Text, Hangul, Long
944
0
                number = NumberChar_Hangul_ko;
945
0
                type = attLong;
946
0
                break;
947
0
            case NativeNumberMode::NATNUM11:        // Text, Hangul, Short
948
0
                number = NumberChar_Hangul_ko;
949
0
                type = attMedium;
950
0
                break;
951
0
            default:
952
0
                break;
953
0
        }
954
0
    }
955
0
    return NativeNumberXmlAttributes(rLocale, OUString(&NumberChar[number][1], 1),
956
0
            OUString::createFromAscii(attType[type]));
957
0
}
958
959
static bool natNumIn(sal_Int16 num, const sal_Int16 natnum[], sal_Int16 len)
960
0
{
961
0
    for (sal_Int16 i = 0; i < len; i++)
962
0
        if (natnum[i] == num)
963
0
            return true;
964
0
    return false;
965
0
}
966
967
sal_Int16 SAL_CALL NativeNumberSupplierService::convertFromXmlAttributes( const NativeNumberXmlAttributes& aAttr )
968
0
{
969
0
    sal_Unicode numberChar[NumberChar_Count];
970
0
    for (sal_Int16 i = 0; i < NumberChar_Count; i++)
971
0
        numberChar[i] = NumberChar[i][1];
972
0
    OUString number(numberChar, NumberChar_Count);
973
974
0
    sal_Int16 num = sal::static_int_cast<sal_Int16>( number.indexOf(aAttr.Format) );
975
976
0
    if ( aAttr.Style == "short" ) {
977
0
        if (num == NumberChar_FullWidth)
978
0
            return NativeNumberMode::NATNUM3;
979
0
        else if (num == NumberChar_Hangul_ko)
980
0
            return NativeNumberMode::NATNUM9;
981
0
        else if (natNumIn(num, natnum1, sizeof_natnum1))
982
0
            return NativeNumberMode::NATNUM1;
983
0
        else if (natNumIn(num, natnum2, sizeof_natnum2))
984
0
            return NativeNumberMode::NATNUM2;
985
0
    } else if ( aAttr.Style == "medium" ) {
986
0
        if (num == NumberChar_Hangul_ko)
987
0
            return NativeNumberMode::NATNUM11;
988
0
        else if (num == NumberChar_he)
989
0
            return NativeNumberMode::NATNUM2;
990
0
        else if (natNumIn(num, natnum1, sizeof_natnum1))
991
0
            return NativeNumberMode::NATNUM7;
992
0
        else if (natNumIn(num, natnum2, sizeof_natnum2))
993
0
            return NativeNumberMode::NATNUM8;
994
0
    } else if ( aAttr.Style == "long" ) {
995
0
        if (num == NumberChar_FullWidth)
996
0
            return NativeNumberMode::NATNUM6;
997
0
        else if (num == NumberChar_Hangul_ko)
998
0
            return NativeNumberMode::NATNUM10;
999
0
        else if (natNumIn(num, natnum1, sizeof_natnum1))
1000
0
            return NativeNumberMode::NATNUM4;
1001
0
        else if (natNumIn(num, natnum2, sizeof_natnum2))
1002
0
            return NativeNumberMode::NATNUM5;
1003
0
    } else {
1004
0
        throw RuntimeException();
1005
0
    }
1006
0
    return NativeNumberMode::NATNUM0;
1007
0
}
1008
1009
1010
// Following code generates Hebrew Number,
1011
// see numerical system in the Hebrew Numbering System in following link for details,
1012
// http://smontagu.org/writings/HebrewNumbers.html
1013
1014
namespace {
1015
1016
struct HebrewNumberChar {
1017
    sal_Unicode code;
1018
    sal_Int16 value;
1019
};
1020
1021
}
1022
1023
HebrewNumberChar const HebrewNumberCharArray[] = {
1024
    { 0x05ea, 400 },
1025
    { 0x05ea, 400 },
1026
    { 0x05e9, 300 },
1027
    { 0x05e8, 200 },
1028
    { 0x05e7, 100 },
1029
    { 0x05e6, 90 },
1030
    { 0x05e4, 80 },
1031
    { 0x05e2, 70 },
1032
    { 0x05e1, 60 },
1033
    { 0x05e0, 50 },
1034
    { 0x05de, 40 },
1035
    { 0x05dc, 30 },
1036
    { 0x05db, 20 },
1037
    { 0x05d9, 10 },
1038
    { 0x05d8, 9 },
1039
    { 0x05d7, 8 },
1040
    { 0x05d6, 7 },
1041
    { 0x05d5, 6 },
1042
    { 0x05d4, 5 },
1043
    { 0x05d3, 4 },
1044
    { 0x05d2, 3 },
1045
    { 0x05d1, 2 },
1046
    { 0x05d0, 1 }
1047
};
1048
1049
const sal_Unicode thousand[] = {0x05d0, 0x05dc, 0x05e3, 0x0};
1050
const sal_Unicode thousands[] = {0x05d0, 0x05dc, 0x05e4, 0x05d9, 0x0};
1051
const sal_Unicode thousands_last[] = {0x05d0, 0x05dc, 0x05e4, 0x05d9, 0x05dd, 0x0};
1052
const sal_Unicode geresh = 0x05f3;
1053
const sal_Unicode gershayim = 0x05f4;
1054
1055
static void makeHebrewNumber(sal_Int64 value, OUStringBuffer& output, bool isLast, bool useGeresh)
1056
3.38k
{
1057
3.38k
    sal_Int16 num = sal::static_int_cast<sal_Int16>(value % 1000);
1058
1059
3.38k
    if (value > 1000) {
1060
406
        makeHebrewNumber(value / 1000, output, num != 0, useGeresh);
1061
406
        output.append(" ");
1062
406
    }
1063
3.38k
    if (num == 0) {
1064
0
        output.append(value == 1000 ? thousand : isLast ? thousands_last : thousands);
1065
3.38k
    } else {
1066
3.38k
        sal_Int16 nbOfChar = 0;
1067
67.4k
        for (sal_Int32 j = 0; num > 0 && j < sal_Int32(std::size(HebrewNumberCharArray)); j++) {
1068
64.0k
            if (num - HebrewNumberCharArray[j].value >= 0) {
1069
8.83k
                nbOfChar++;
1070
                // https://en.wikipedia.org/wiki/Hebrew_numerals#Key_exceptions
1071
                // By convention, the numbers 15 and 16 are represented as 9 + 6 and 9 + 7
1072
8.83k
                if (num == 15 || num == 16) // substitution for 15 and 16
1073
8
                    j++;
1074
8.83k
                assert(j < sal_Int32(SAL_N_ELEMENTS(HebrewNumberCharArray)));
1075
8.83k
                num = sal::static_int_cast<sal_Int16>( num - HebrewNumberCharArray[j].value );
1076
8.83k
                output.append(HebrewNumberCharArray[j].code);
1077
8.83k
            }
1078
64.0k
        }
1079
3.38k
        if (useGeresh) {
1080
3.37k
            if (nbOfChar > 1)   // a number is written as more than one character
1081
2.72k
                output.insert(output.getLength() - 1, gershayim);
1082
653
            else if (nbOfChar == 1) // a number is written as a single character
1083
653
                output.append(geresh);
1084
3.37k
        }
1085
3.38k
    }
1086
3.38k
}
1087
1088
OUString getHebrewNativeNumberString(const OUString& aNumberString, bool useGeresh)
1089
3.00k
{
1090
3.00k
    sal_Int64 value = 0;
1091
3.00k
    sal_Int32 i, count = 0, len = aNumberString.getLength();
1092
3.00k
    const sal_Unicode *src = aNumberString.getStr();
1093
1094
10.7k
    for (i = 0; i < len; i++) {
1095
7.78k
        sal_Unicode ch = src[i];
1096
7.78k
        if (isNumber(ch)) {
1097
7.76k
            if (++count >= 20) // Number is too long, could not be handled.
1098
0
                return aNumberString;
1099
7.76k
            value = value * 10 + (ch - NUMBER_ZERO);
1100
7.76k
        }
1101
26
        else if (isSeparator(ch) && count > 0) continue;
1102
26
        else if (isMinus(ch) && count == 0) continue;
1103
26
        else break;
1104
7.78k
    }
1105
1106
3.00k
    if (value > 0) {
1107
2.98k
        OUStringBuffer output(count*2 + 2 + len - i);
1108
1109
2.98k
        makeHebrewNumber(value, output, true, useGeresh);
1110
1111
2.98k
        if (i < len)
1112
0
            output.append(aNumberString.subView(i));
1113
1114
2.98k
        return output.makeStringAndClear();
1115
2.98k
    }
1116
26
    else
1117
26
        return aNumberString;
1118
3.00k
}
1119
1120
// Support for Cyrillic Numerals
1121
// See UTN 41 for implementation information
1122
// http://www.unicode.org/notes/tn41/
1123
1124
const sal_Unicode cyrillicThousandsMark = 0x0482;
1125
const sal_Unicode cyrillicTitlo = 0x0483;
1126
const sal_Unicode cyrillicTen = 0x0456;
1127
1128
namespace {
1129
1130
struct CyrillicNumberChar {
1131
    sal_Unicode code;
1132
    sal_Int16 value;
1133
};
1134
1135
}
1136
1137
CyrillicNumberChar const CyrillicNumberCharArray[] = {
1138
    { 0x0446, 900 },
1139
    { 0x047f, 800 },
1140
    { 0x0471, 700 },
1141
    { 0x0445, 600 },
1142
    { 0x0444, 500 },
1143
    { 0x0443, 400 },
1144
    { 0x0442, 300 },
1145
    { 0x0441, 200 },
1146
    { 0x0440, 100 },
1147
    { 0x0447, 90 },
1148
    { 0x043f, 80 },
1149
    { 0x047b, 70 },
1150
    { 0x046f, 60 },
1151
    { 0x043d, 50 },
1152
    { 0x043c, 40 },
1153
    { 0x043b, 30 },
1154
    { 0x043a, 20 },
1155
    { 0x0456, 10 },
1156
    { 0x0473, 9 },
1157
    { 0x0438, 8 },
1158
    { 0x0437, 7 },
1159
    { 0x0455, 6 },
1160
    { 0x0454, 5 },
1161
    { 0x0434, 4 },
1162
    { 0x0433, 3 },
1163
    { 0x0432, 2 },
1164
    { 0x0430, 1 }
1165
};
1166
1167
static void makeCyrillicNumber(sal_Int64 value, OUStringBuffer& output, bool addTitlo)
1168
0
{
1169
0
    sal_Int16 num = sal::static_int_cast<sal_Int16>(value % 1000);
1170
0
    if (value >= 1000) {
1171
0
        output.append(cyrillicThousandsMark);
1172
0
        makeCyrillicNumber(value / 1000, output, false);
1173
0
        if (value >= 10000 && (value - 10000) % 1000 != 0) {
1174
0
            output.append(" ");
1175
0
        }
1176
0
        if (value % 1000 == 0)
1177
0
            addTitlo = false;
1178
0
    }
1179
1180
0
    for (sal_Int32 j = 0; num > 0 && j < sal_Int32(std::size(CyrillicNumberCharArray)); j++) {
1181
0
        if (num < 20 && num > 10) {
1182
0
            num -= 10;
1183
0
            makeCyrillicNumber(num, output, false);
1184
0
            output.append(cyrillicTen);
1185
0
            break;
1186
0
        }
1187
1188
0
        if (CyrillicNumberCharArray[j].value <= num) {
1189
0
            output.append(CyrillicNumberCharArray[j].code);
1190
0
            num = sal::static_int_cast<sal_Int16>( num - CyrillicNumberCharArray[j].value );
1191
0
        }
1192
0
    }
1193
1194
0
    if (!addTitlo)
1195
0
        return;
1196
1197
0
    if (output.getLength() == 1) {
1198
0
        output.append(cyrillicTitlo);
1199
0
    } else if (output.getLength() == 2) {
1200
0
        if (value > 800 && value < 900) {
1201
0
            output.append(cyrillicTitlo);
1202
0
        } else {
1203
0
            output.insert(1, cyrillicTitlo);
1204
0
        }
1205
0
    } else if (output.getLength() > 2) {
1206
0
        if (output.indexOf(" ") == output.getLength() - 2) {
1207
0
            output.append(cyrillicTitlo);
1208
0
        } else {
1209
0
            output.insert(output.getLength() - 1, cyrillicTitlo);
1210
0
        }
1211
0
    }
1212
0
}
1213
1214
OUString getCyrillicNativeNumberString(const OUString& aNumberString)
1215
0
{
1216
0
    sal_Int64 value = 0;
1217
0
    sal_Int32 i, count = 0, len = aNumberString.getLength();
1218
0
    const sal_Unicode *src = aNumberString.getStr();
1219
1220
0
    for (i = 0; i < len; i++) {
1221
0
        sal_Unicode ch = src[i];
1222
0
        if (isNumber(ch)) {
1223
0
            if (++count >= 8) // Number is too long, could not be handled.
1224
0
                return aNumberString;
1225
0
            value = value * 10 + (ch - NUMBER_ZERO);
1226
0
        }
1227
0
        else if (isSeparator(ch) && count > 0) continue;
1228
0
        else if (isMinus(ch) && count == 0) continue;
1229
0
        else break;
1230
0
    }
1231
1232
0
    if (value > 0) {
1233
0
        OUStringBuffer output(count*2 + 2 + len - i);
1234
1235
0
        makeCyrillicNumber(value, output, true);
1236
1237
0
        if (i < len)
1238
0
            output.append(aNumberString.subView(i));
1239
1240
0
        return output.makeStringAndClear();
1241
0
    }
1242
0
    else
1243
0
        return aNumberString;
1244
0
}
1245
1246
constexpr OUString implementationName = u"com.sun.star.i18n.NativeNumberSupplier"_ustr;
1247
1248
OUString SAL_CALL NativeNumberSupplierService::getImplementationName()
1249
0
{
1250
0
    return implementationName;
1251
0
}
1252
1253
sal_Bool SAL_CALL
1254
NativeNumberSupplierService::supportsService(const OUString& rServiceName)
1255
0
{
1256
0
    return cppu::supportsService(this, rServiceName);
1257
0
}
1258
1259
Sequence< OUString > SAL_CALL
1260
NativeNumberSupplierService::getSupportedServiceNames()
1261
0
{
1262
0
    return {implementationName, u"com.sun.star.i18n.NativeNumberSupplier2"_ustr};
1263
0
}
1264
1265
}
1266
1267
extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
1268
com_sun_star_i18n_NativeNumberSupplier_get_implementation(
1269
    css::uno::XComponentContext *,
1270
    css::uno::Sequence<css::uno::Any> const &)
1271
137k
{
1272
137k
    return cppu::acquire(new i18npool::NativeNumberSupplierService());
1273
137k
}
1274
1275
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */