Coverage Report

Created: 2026-07-10 11:04

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.96k
#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.45k
#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
27.0k
{
88
27.0k
    const sal_Unicode *src = inStr.getStr();
89
27.0k
    rtl_uString *newStr = rtl_uString_alloc(nCount);
90
27.0k
    if (pOffset)
91
0
        pOffset->realloc(nCount);
92
27.0k
    auto ppOffset = pOffset ? pOffset->getArray() : nullptr;
93
94
97.7k
    for (sal_Int32 i = 0; i < nCount; i++)
95
70.6k
    {
96
70.6k
        sal_Unicode ch = src[i];
97
70.6k
        if (isNumber(ch))
98
66.1k
            newStr->buffer[i] = NumberChar[number][ ch - NUMBER_ZERO ];
99
4.48k
        else if (i+1 < nCount && isNumber(src[i+1])) {
100
3.97k
            if (i > 0 && isNumber(src[i-1]) && isSeparator(ch))
101
0
                newStr->buffer[i] = SeparatorChar[number] ? SeparatorChar[number] : ch;
102
3.97k
            else
103
3.97k
                newStr->buffer[i] = isDecimal(ch) ? (DecimalChar[number] ? DecimalChar[number] : ch) :
104
3.97k
                    isMinus(ch) ? (MinusChar[number] ? MinusChar[number] : ch) : ch;
105
3.97k
        }
106
510
        else
107
510
            newStr->buffer[i] = ch;
108
70.6k
        if (ppOffset)
109
0
            ppOffset[i] = i;
110
70.6k
    }
111
27.0k
    return OUString(newStr, SAL_NO_ACQUIRE); // take ownership
112
27.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
45.0k
{
118
45.0k
    sal_Unicode multiChar = (multiChar_index == -1 ? 0 : number->multiplierChar[multiChar_index]);
119
45.0k
    auto ppOffset = pOffset ? pOffset->getArray() : nullptr;
120
45.0k
    if ( len <= number->multiplierExponent[number->exponentCount-1] ) {
121
38.0k
        if (number->multiplierExponent[number->exponentCount-1] > 1) {
122
4.68k
            bool bNotZero = false;
123
9.37k
            for (const sal_Int32 end = begin+len; begin < end; begin++) {
124
4.68k
                if (bNotZero || str[begin] != NUMBER_ZERO) {
125
4.68k
                    dst[count] = numberChar[str[begin] - NUMBER_ZERO];
126
4.68k
                    if (ppOffset)
127
0
                        ppOffset[count] = begin + startPos;
128
4.68k
                    count++;
129
4.68k
                    bNotZero = true;
130
4.68k
                }
131
4.68k
            }
132
4.68k
            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
4.68k
            return bNotZero;
139
33.3k
        } else if (str[begin] != NUMBER_ZERO) {
140
31.3k
            if (!(number->numberFlag & (multiChar_index < 0 ? 0 : NUMBER_OMIT_ONE_CHECK(multiChar_index))) || str[begin] != NUMBER_ONE) {
141
25.0k
                dst[count] = numberChar[str[begin] - NUMBER_ZERO];
142
25.0k
                if (ppOffset)
143
0
                    ppOffset[count] = begin + startPos;
144
25.0k
                count++;
145
25.0k
            }
146
31.3k
            if (multiChar > 0) {
147
6.45k
                dst[count] = multiChar;
148
6.45k
                if (ppOffset)
149
0
                    ppOffset[count] = begin + startPos;
150
6.45k
                count++;
151
6.45k
            }
152
31.3k
        } 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
33.3k
        return str[begin] != NUMBER_ZERO;
159
38.0k
    } else {
160
7.04k
        bool bPrintPower = false;
161
        // sal_Int16 last = 0;
162
49.9k
        for (sal_Int16 i = 1; i <= number->exponentCount; i++) {
163
42.8k
            sal_Int32 tmp = len - (i == number->exponentCount ? 0 : number->multiplierExponent[i]);
164
42.8k
            if (tmp > 0) {
165
14.0k
                bPrintPower |= AsciiToNative_numberMaker(str, begin, tmp, dst, count,
166
14.0k
                        (i == number->exponentCount ? -1 : i), pOffset, startPos, number, numberChar);
167
14.0k
                begin += tmp;
168
14.0k
                len -= tmp;
169
14.0k
            }
170
42.8k
        }
171
7.04k
        if (bPrintPower) {
172
7.04k
            if (count > 0 && number->multiplierExponent[number->exponentCount-1] == 1 &&
173
7.04k
                    dst[count-1] == numberChar[0])
174
0
                count--;
175
7.04k
            if (multiChar > 0) {
176
0
                dst[count] = multiChar;
177
0
                if (ppOffset)
178
0
                    ppOffset[count] = begin + startPos;
179
0
                count++;
180
0
            }
181
7.04k
        }
182
7.04k
        return bPrintPower;
183
7.04k
    }
184
45.0k
}
185
186
/// @throws RuntimeException
187
static OUString AsciiToNative( const OUString& inStr, sal_Int32 nCount,
188
        Sequence< sal_Int32 >* pOffset, const Number* number )
189
30.9k
{
190
30.9k
    OUString aRet;
191
192
30.9k
    sal_Int32 strLen = inStr.getLength();
193
30.9k
    const sal_Unicode *numberChar = NumberChar[number->number];
194
195
30.9k
    if (nCount > strLen)
196
0
        nCount = strLen;
197
198
30.9k
    if (nCount > 0)
199
30.9k
    {
200
30.9k
        const sal_Unicode *str = inStr.getStr();
201
30.9k
        std::unique_ptr<sal_Unicode[]> newStr(new sal_Unicode[nCount * 2 + 1]);
202
30.9k
        std::unique_ptr<sal_Unicode[]> srcStr(new sal_Unicode[nCount + 1]); // for keeping number without comma
203
30.9k
        sal_Int32 i, len = 0, count = 0;
204
205
30.9k
        if (pOffset)
206
0
            pOffset->realloc( nCount * 2 );
207
30.9k
        auto ppOffset = pOffset ? pOffset->getArray() : nullptr;
208
30.9k
        bool bDoDecimal = false;
209
210
99.9k
        for (i = 0; i <= nCount; i++)
211
68.9k
        {
212
68.9k
            if (i < nCount && isNumber(str[i])) {
213
38.0k
                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
38.0k
                else
220
38.0k
                    srcStr[len++] = str[i];
221
38.0k
            } else {
222
30.9k
                if (len > 0) {
223
30.9k
                    if (i < nCount-1 && isSeparator(str[i]) && isNumber(str[i+1]))
224
0
                        continue; // skip comma inside number string
225
30.9k
                    bool bNotZero = false;
226
30.9k
                    for (sal_Int32 begin = 0, end = len % number->multiplierExponent[0];
227
61.9k
                            end <= len; begin = end, end += number->multiplierExponent[0]) {
228
30.9k
                        if (end == 0) continue;
229
30.9k
                        sal_Int32 _count = count;
230
30.9k
                        bNotZero |= AsciiToNative_numberMaker(srcStr.get(), begin, end - begin, newStr.get(), count,
231
30.9k
                                end == len ? -1 : 0, pOffset, i - len, number, numberChar);
232
30.9k
                        if (count > 0 && number->multiplierExponent[number->exponentCount-1] == 1 &&
233
26.2k
                                newStr[count-1] == numberChar[0])
234
0
                            count--;
235
30.9k
                        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
30.9k
                    }
242
30.9k
                    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
30.9k
                    len = 0;
249
30.9k
                }
250
30.9k
                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
30.9k
            }
265
68.9k
        }
266
267
30.9k
        if (pOffset)
268
0
            pOffset->realloc(count);
269
30.9k
        aRet = OUString(newStr.get(), count);
270
30.9k
    }
271
30.9k
    return aRet;
272
30.9k
}
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.1k
        for ( i = 0; i < nCount; i++) {
355
21.6k
            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.6k
            } else {
368
21.6k
                if ((index = numberChar.indexOf(str[i])) >= 0)
369
3.24k
                    newStr[count] = sal::static_int_cast<sal_Unicode>( (index % 10) + NUMBER_ZERO );
370
18.4k
                else if (separatorChar.indexOf(str[i]) >= 0 &&
371
654
                        (i < nCount-1 && (numberChar.indexOf(str[i+1]) >= 0 ||
372
529
                                          multiplierChar.indexOf(str[i+1]) >= 0)))
373
73
                    newStr[count] = SeparatorChar[NumberChar_HalfWidth];
374
18.3k
                else if (decimalChar.indexOf(str[i]) >= 0 &&
375
852
                        (i < nCount-1 && (numberChar.indexOf(str[i+1]) >= 0 ||
376
705
                                          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
137
                    newStr[count] = DecimalChar[NumberChar_HalfWidth];
380
18.2k
                else if (minusChar.find(str[i]) != std::u16string_view::npos &&
381
1.11k
                        (i < nCount-1 && (numberChar.indexOf(str[i+1]) >= 0 ||
382
665
                                          multiplierChar.indexOf(str[i+1]) >= 0)))
383
                    // Only when minus is followed by numbers,
384
                    // it will be convert to ASCII minus sign
385
273
                    newStr[count] = MinusChar[NumberChar_HalfWidth];
386
17.9k
                else
387
17.9k
                    newStr[count] = str[i];
388
21.6k
                if (ppOffset)
389
0
                    ppOffset[count] = i;
390
21.6k
                count++;
391
21.6k
            }
392
21.6k
        }
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
195k
{
539
    // return zh_TW for TW, HK and MO, return zh_CN for other zh locales.
540
195k
    if (rLocale.Language == "zh") return MsLangId::isTraditionalChinese(rLocale) ? 1 : 0;
541
542
1.92M
    for (sal_Int16 i = 2; i < nbOfLocale; i++)
543
1.85M
        if (rLocale.Language.equalsAsciiL(natnum1Locales[i], 2))
544
54.4k
            return i;
545
546
72.6k
    return -1;
547
127k
}
548
549
struct Separators
550
{
551
    sal_Unicode DecimalSeparator;
552
    sal_Unicode ThousandSeparator;
553
    Separators(const Locale& rLocale)
554
4
    {
555
4
        LocaleDataItem aLocaleItem = LocaleDataImpl::get()->getLocaleItem(rLocale);
556
4
        DecimalSeparator = aLocaleItem.decimalSeparator.toChar();
557
4
        ThousandSeparator = aLocaleItem.thousandSeparator.toChar();
558
4
    }
559
};
560
561
Separators getLocaleSeparators(const Locale& rLocale, const OUString& rLocStr)
562
1.56k
{
563
    // Guard the static variable below.
564
1.56k
    std::scoped_lock aGuard(theNatNumMutex);
565
    // Maximum a couple hundred of pairs with 4-byte structs - so no need for smart managing
566
1.56k
    static std::unordered_map<OUString, Separators> aLocaleSeparatorsBuf;
567
1.56k
    auto it = aLocaleSeparatorsBuf.find(rLocStr);
568
1.56k
    if (it == aLocaleSeparatorsBuf.end())
569
4
    {
570
4
        it = aLocaleSeparatorsBuf.emplace(rLocStr, Separators(rLocale)).first;
571
4
    }
572
1.56k
    return it->second;
573
1.56k
}
574
575
OUString getNumberText(const Locale& rLocale, const OUString& rNumberString,
576
                       std::u16string_view sNumberTextParams)
577
1.56k
{
578
1.56k
    sal_Int32 i, count = 0;
579
1.56k
    const sal_Int32 len = rNumberString.getLength();
580
1.56k
    const sal_Unicode* src = rNumberString.getStr();
581
582
1.56k
    OUString aLoc = LanguageTag::convertToBcp47(rLocale);
583
1.56k
    Separators aSeparators = getLocaleSeparators(rLocale, aLoc);
584
585
1.56k
    OUStringBuffer sBuf(len);
586
3.12k
    for (i = 0; i < len; i++)
587
1.56k
    {
588
1.56k
        sal_Unicode ch = src[i];
589
1.56k
        if (isNumber(ch) || ch == aSeparators.DecimalSeparator)
590
1.56k
        {
591
1.56k
            ++count;
592
1.56k
            sBuf.append(ch);
593
1.56k
        }
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
1.56k
    }
601
602
    // Handle also month and day names for NatNum12 date formatting
603
1.56k
    const OUString aNumberStr = (count == 0) ? rNumberString : sBuf.makeStringAndClear();
604
605
1.56k
    static auto xNumberText
606
1.56k
        = css::linguistic2::NumberText::create(comphelper::getProcessComponentContext());
607
608
    // Guard the static variables below.
609
1.56k
    std::scoped_lock aGuard( theNatNumMutex );
610
611
1.56k
    OUString numbertext_prefix;
612
    // default "cardinal" gets empty prefix
613
1.56k
    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
1.56k
    static std::unordered_map<OUString, std::map<OUString, OUString>> aBuff;
619
1.56k
    auto& rItems = aBuff[aNumberStr];
620
1.56k
    auto& rItem = rItems[numbertext_prefix + aLoc];
621
1.56k
    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
1.56k
    OUString sResult = rItem;
629
1.56k
    if (i != 0 && i < len)
630
0
        sResult += rNumberString.subView(i);
631
1.56k
    return sResult;
632
1.56k
}
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
116k
{
640
116k
    if (!isValidNatNumImpl(rLocale, nNativeNumberMode))
641
36.1k
        return aNumberString;
642
643
80.4k
    if (nNativeNumberMode == NativeNumberMode::NATNUM12)
644
1.56k
    {
645
        // handle capitalization prefixes "capitalize", "upper", "lower" and "title"
646
647
1.56k
        enum WhichCasing
648
1.56k
        {
649
1.56k
            CAPITALIZE,
650
1.56k
            UPPER,
651
1.56k
            LOWER,
652
1.56k
            TITLE
653
1.56k
        };
654
655
1.56k
        struct CasingEntry
656
1.56k
        {
657
1.56k
            std::u16string_view aLiteral;
658
1.56k
            WhichCasing     eCasing;
659
1.56k
        };
660
661
1.56k
        static const CasingEntry Casings[] =
662
1.56k
        {
663
1.56k
            { std::u16string_view(u"capitalize"), CAPITALIZE },
664
1.56k
            { std::u16string_view(u"upper"), UPPER },
665
1.56k
            { std::u16string_view(u"lower"), LOWER },
666
1.56k
            { std::u16string_view(u"title"), TITLE }
667
1.56k
        };
668
669
1.56k
        std::size_t nStripCase = 0;
670
1.56k
        size_t nCasing;
671
7.81k
        for (nCasing = 0; nCasing < std::size(Casings); ++nCasing)
672
6.25k
        {
673
6.25k
            if (o3tl::starts_with(rNativeNumberParams, Casings[nCasing].aLiteral))
674
0
            {
675
0
                nStripCase = Casings[nCasing].aLiteral.size();
676
0
                break;
677
0
            }
678
6.25k
        }
679
680
1.56k
        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
1.56k
        else
718
1.56k
        {
719
1.56k
            return getNumberText(rLocale, aNumberString, rNativeNumberParams);
720
1.56k
        }
721
1.56k
    }
722
723
78.8k
    sal_Int16 langnum = getLanguageNumber(rLocale);
724
78.8k
    if (langnum == -1)
725
17.6k
        return aNumberString;
726
727
61.2k
    const Number *number = nullptr;
728
61.2k
    sal_Int16 num = -1;
729
730
61.2k
    switch (nNativeNumberMode)
731
61.2k
    {
732
436
        case NativeNumberMode::NATNUM0: // Ascii
733
436
            return NativeToAscii(aNumberString, aNumberString.getLength(), pOffset);
734
4.70k
        case NativeNumberMode::NATNUM1: // Char, Lower
735
4.70k
            num = natnum1[langnum];
736
4.70k
            break;
737
20.4k
        case NativeNumberMode::NATNUM2: // Char, Upper
738
20.4k
            num = natnum2[langnum];
739
20.4k
            break;
740
3
        case NativeNumberMode::NATNUM3: // Char, FullWidth
741
3
            num = NumberChar_FullWidth;
742
3
            break;
743
0
        case NativeNumberMode::NATNUM4: // Text, Lower, Long
744
0
            number = &natnum4[langnum];
745
0
            break;
746
9.97k
        case NativeNumberMode::NATNUM5: // Text, Upper, Long
747
9.97k
            number = &natnum5[langnum];
748
9.97k
            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
16.3k
        case NativeNumberMode::NATNUM8: // Text, Upper, Short
756
16.3k
            number = &natnum8[langnum];
757
16.3k
            break;
758
4.68k
        case NativeNumberMode::NATNUM9: // Char, Hangul
759
4.68k
            num = NumberChar_Hangul_ko;
760
4.68k
            break;
761
0
        case NativeNumberMode::NATNUM10:        // Text, Hangul, Long
762
0
            number = &natnum10;
763
0
            break;
764
4.68k
        case NativeNumberMode::NATNUM11:        // Text, Hangul, Short
765
4.68k
            number = &natnum11;
766
4.68k
            break;
767
0
        default:
768
0
            break;
769
61.2k
    }
770
771
60.7k
    if (number || num >= 0) {
772
60.7k
        if (aLocale.Language != rLocale.Language ||
773
55.5k
                aLocale.Country != rLocale.Country ||
774
54.0k
                aLocale.Variant != rLocale.Variant) {
775
6.78k
            LocaleDataItem item = LocaleDataImpl::get()->getLocaleItem( rLocale );
776
6.78k
            aLocale = rLocale;
777
6.78k
            DecimalChar[NumberChar_HalfWidth]=item.decimalSeparator.toChar();
778
6.78k
            if (DecimalChar[NumberChar_HalfWidth] > 0x7E || DecimalChar[NumberChar_HalfWidth] < 0x21)
779
0
                DecimalChar[NumberChar_FullWidth]=0xFF0E;
780
6.78k
            else
781
6.78k
                DecimalChar[NumberChar_FullWidth]=DecimalChar[NumberChar_HalfWidth]+0xFEE0;
782
6.78k
            SeparatorChar[NumberChar_HalfWidth]=item.thousandSeparator.toChar();
783
6.78k
            if (SeparatorChar[NumberChar_HalfWidth] > 0x7E || SeparatorChar[NumberChar_HalfWidth] < 0x21)
784
0
                SeparatorChar[NumberChar_FullWidth]=0xFF0C;
785
6.78k
            else
786
6.78k
                SeparatorChar[NumberChar_FullWidth]=SeparatorChar[NumberChar_HalfWidth]+0xFEE0;
787
6.78k
        }
788
60.7k
        if (number)
789
30.9k
            return AsciiToNative( aNumberString, aNumberString.getLength(), pOffset, number );
790
29.8k
        else if (num == NumberChar_he)
791
2.79k
            return getHebrewNativeNumberString(aNumberString,
792
2.79k
                    nNativeNumberMode == NativeNumberMode::NATNUM2);
793
27.0k
        else if (num == NumberChar_cu)
794
0
            return getCyrillicNativeNumberString(aNumberString);
795
27.0k
        else
796
27.0k
            return AsciiToNativeChar(aNumberString, aNumberString.getLength(), pOffset, num);
797
60.7k
    }
798
0
    else
799
0
        return aNumberString;
800
60.7k
}
801
802
OUString SAL_CALL NativeNumberSupplierService::getNativeNumberString(const OUString& aNumberString, const Locale& rLocale,
803
                sal_Int16 nNativeNumberMode)
804
76.5k
{
805
76.5k
    return getNativeNumberString(aNumberString, rLocale, nNativeNumberMode, nullptr);
806
76.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
40.0k
{
812
40.0k
    return getNativeNumberString(rNumberString, rLocale, nNativeNumberMode, nullptr, rNativeNumberParams);
813
40.0k
}
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
116k
{
861
116k
    sal_Int16 langnum = getLanguageNumber(rLocale);
862
863
116k
    switch (nNativeNumberMode) {
864
13.3k
        case NativeNumberMode::NATNUM0:     // Ascii
865
18.0k
        case NativeNumberMode::NATNUM3:     // Char, FullWidth
866
19.6k
        case NativeNumberMode::NATNUM12:    // spell out numbers, dates and money amounts
867
19.6k
            return true;
868
5.57k
        case NativeNumberMode::NATNUM1:     // Char, Lower
869
5.57k
            return (langnum >= 0);
870
55.7k
        case NativeNumberMode::NATNUM2:     // Char, Upper
871
55.7k
            if (langnum == 4) // Hebrew numbering
872
2.78k
                return true;
873
52.9k
            [[fallthrough]];
874
52.9k
        case NativeNumberMode::NATNUM4:     // Text, Lower, Long
875
62.9k
        case NativeNumberMode::NATNUM5:     // Text, Upper, Long
876
62.9k
        case NativeNumberMode::NATNUM6:     // Text, FullWidth
877
62.9k
        case NativeNumberMode::NATNUM7:     // Text. Lower, Short
878
79.2k
        case NativeNumberMode::NATNUM8:     // Text, Upper, Short
879
79.2k
            return (langnum >= 0 && langnum < 4); // CJK numbering
880
4.68k
        case NativeNumberMode::NATNUM9:     // Char, Hangul
881
4.68k
        case NativeNumberMode::NATNUM10:    // Text, Hangul, Long
882
9.37k
        case NativeNumberMode::NATNUM11:    // Text, Hangul, Short
883
9.37k
            return (langnum == 3); // Korean numbering
884
116k
    }
885
0
    return false;
886
116k
}
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.18k
{
1057
3.18k
    sal_Int16 num = sal::static_int_cast<sal_Int16>(value % 1000);
1058
1059
3.18k
    if (value > 1000) {
1060
422
        makeHebrewNumber(value / 1000, output, num != 0, useGeresh);
1061
422
        output.append(" ");
1062
422
    }
1063
3.18k
    if (num == 0) {
1064
0
        output.append(value == 1000 ? thousand : isLast ? thousands_last : thousands);
1065
3.18k
    } else {
1066
3.18k
        sal_Int16 nbOfChar = 0;
1067
63.1k
        for (sal_Int32 j = 0; num > 0 && j < sal_Int32(std::size(HebrewNumberCharArray)); j++) {
1068
59.9k
            if (num - HebrewNumberCharArray[j].value >= 0) {
1069
8.25k
                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.25k
                if (num == 15 || num == 16) // substitution for 15 and 16
1073
8
                    j++;
1074
8.25k
                assert(j < sal_Int32(SAL_N_ELEMENTS(HebrewNumberCharArray)));
1075
8.25k
                num = sal::static_int_cast<sal_Int16>( num - HebrewNumberCharArray[j].value );
1076
8.25k
                output.append(HebrewNumberCharArray[j].code);
1077
8.25k
            }
1078
59.9k
        }
1079
3.18k
        if (useGeresh) {
1080
3.17k
            if (nbOfChar > 1)   // a number is written as more than one character
1081
2.53k
                output.insert(output.getLength() - 1, gershayim);
1082
642
            else if (nbOfChar == 1) // a number is written as a single character
1083
642
                output.append(geresh);
1084
3.17k
        }
1085
3.18k
    }
1086
3.18k
}
1087
1088
OUString getHebrewNativeNumberString(const OUString& aNumberString, bool useGeresh)
1089
2.79k
{
1090
2.79k
    sal_Int64 value = 0;
1091
2.79k
    sal_Int32 i, count = 0, len = aNumberString.getLength();
1092
2.79k
    const sal_Unicode *src = aNumberString.getStr();
1093
1094
10.0k
    for (i = 0; i < len; i++) {
1095
7.30k
        sal_Unicode ch = src[i];
1096
7.30k
        if (isNumber(ch)) {
1097
7.26k
            if (++count >= 20) // Number is too long, could not be handled.
1098
0
                return aNumberString;
1099
7.26k
            value = value * 10 + (ch - NUMBER_ZERO);
1100
7.26k
        }
1101
35
        else if (isSeparator(ch) && count > 0) continue;
1102
35
        else if (isMinus(ch) && count == 0) continue;
1103
35
        else break;
1104
7.30k
    }
1105
1106
2.79k
    if (value > 0) {
1107
2.75k
        OUStringBuffer output(count*2 + 2 + len - i);
1108
1109
2.75k
        makeHebrewNumber(value, output, true, useGeresh);
1110
1111
2.75k
        if (i < len)
1112
0
            output.append(aNumberString.subView(i));
1113
1114
2.75k
        return output.makeStringAndClear();
1115
2.75k
    }
1116
35
    else
1117
35
        return aNumberString;
1118
2.79k
}
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
141k
{
1272
141k
    return cppu::acquire(new i18npool::NativeNumberSupplierService());
1273
141k
}
1274
1275
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */