/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: */ |