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