Add 'reference' to the iterator_traits, needed by LegacyIterator reqs
[LibreOffice.git] / svl / source / numbers / zformat.cxx
blobfb9cbf50493d0c90fb7131f8a269ea9f25879dc7
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <stdio.h>
21 #include <string_view>
23 #include <comphelper/string.hxx>
24 #include <sal/log.hxx>
25 #include <tools/debug.hxx>
26 #include <tools/long.hxx>
27 #include <i18nlangtag/mslangid.hxx>
28 #include <rtl/math.hxx>
29 #include <unotools/charclass.hxx>
30 #include <unotools/calendarwrapper.hxx>
31 #include <unotools/nativenumberwrapper.hxx>
32 #include <com/sun/star/i18n/CalendarFieldIndex.hpp>
33 #include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
34 #include <com/sun/star/i18n/CalendarDisplayCode.hpp>
35 #include <com/sun/star/i18n/AmPmValue.hpp>
36 #include <com/sun/star/i18n/NativeNumberMode.hpp>
37 #include <com/sun/star/i18n/NativeNumberXmlAttributes2.hpp>
39 #include <svl/zformat.hxx>
40 #include "zforscan.hxx"
42 #include "zforfind.hxx"
43 #include <svl/zforlist.hxx>
44 #include <unotools/digitgroupingiterator.hxx>
45 #include <svl/nfsymbol.hxx>
47 #include <cmath>
48 #include <array>
50 using namespace svt;
52 namespace {
54 constexpr OUStringLiteral GREGORIAN = u"gregorian";
56 const sal_uInt16 UPPER_PRECISION = 300; // entirely arbitrary...
57 const double EXP_LOWER_BOUND = 1.0E-4; // prefer scientific notation below this value.
58 const double EXP_ABS_UPPER_BOUND = 1.0E15; // use exponential notation above that absolute value.
59 // Back in time was E16 that lead
60 // to display rounding errors, see
61 // also sal/rtl/math.cxx
62 // doubleToString()
64 } // namespace
66 const double D_MAX_U_INT32 = double(0xffffffff); // 4294967295.0
67 constexpr double D_MAX_INTEGER = (sal_uInt64(1) << 53) - 1;
69 const double D_MAX_D_BY_100 = 1.7E306;
70 const double D_MIN_M_BY_1000 = 2.3E-305;
72 const sal_uInt8 cCharWidths[ 128-32 ] = {
73 1,1,1,2,2,3,2,1,1,1,1,2,1,1,1,1,
74 2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,2,
75 3,2,2,2,2,2,2,3,2,1,2,2,2,3,3,3,
76 2,3,2,2,2,2,2,3,2,2,2,1,1,1,2,2,
77 1,2,2,2,2,2,1,2,2,1,1,2,1,3,2,2,
78 2,2,1,2,1,2,2,2,2,2,2,1,1,1,2,1
81 // static
82 sal_Int32 SvNumberformat::InsertBlanks( OUStringBuffer& r, sal_Int32 nPos, sal_Unicode c )
84 if( c >= 32 )
86 int n = 2; // Default for chars > 128 (HACK!)
87 if( c <= 127 )
89 n = static_cast<int>(cCharWidths[ c - 32 ]);
91 while( n-- )
93 r.insert( nPos++, ' ');
96 return nPos;
99 static tools::Long GetPrecExp( double fAbsVal )
101 DBG_ASSERT( fAbsVal > 0.0, "GetPrecExp: fAbsVal <= 0.0" );
102 if ( fAbsVal < 1e-7 || fAbsVal > 1e7 )
104 // Shear: whether it's faster or not, falls in between 1e6 and 1e7
105 return static_cast<tools::Long>(floor( log10( fAbsVal ) )) + 1;
107 else
109 tools::Long nPrecExp = 1;
110 while( fAbsVal < 1 )
112 fAbsVal *= 10;
113 nPrecExp--;
115 while( fAbsVal >= 10 )
117 fAbsVal /= 10;
118 nPrecExp++;
120 return nPrecExp;
125 * SvNumberformatInfo
126 * */
128 void ImpSvNumberformatInfo::Copy( const ImpSvNumberformatInfo& rNumFor, sal_uInt16 nCnt )
130 for (sal_uInt16 i = 0; i < nCnt; ++i)
132 sStrArray[i] = rNumFor.sStrArray[i];
133 nTypeArray[i] = rNumFor.nTypeArray[i];
135 eScannedType = rNumFor.eScannedType;
136 bThousand = rNumFor.bThousand;
137 nThousand = rNumFor.nThousand;
138 nCntPre = rNumFor.nCntPre;
139 nCntPost = rNumFor.nCntPost;
140 nCntExp = rNumFor.nCntExp;
143 const std::map<LanguageType, std::array<sal_uInt8, 4>> tblDBNumToNatNum
144 = { { primary(LANGUAGE_CHINESE), { 4, 5, 3, 0 } },
145 { primary(LANGUAGE_JAPANESE), { 4, 5, 3, 0 } },
146 { primary(LANGUAGE_KOREAN), { 4, 5, 6, 10 } } };
148 // static
149 sal_uInt8 SvNumberNatNum::MapDBNumToNatNum( sal_uInt8 nDBNum, LanguageType eLang, bool bDate )
151 sal_uInt8 nNatNum = 0;
152 eLang = MsLangId::getRealLanguage( eLang ); // resolve SYSTEM etc.
153 eLang = primary(eLang); // 10 bit primary language
154 if ( bDate )
156 if ( nDBNum == 4 && eLang == primary(LANGUAGE_KOREAN) )
158 nNatNum = 10;
160 else if ( nDBNum <= 3 )
162 nNatNum = nDBNum; // known to be good for: zh,ja,ko / 1,2,3
165 else
167 if (1 <= nDBNum && nDBNum <= 4)
169 auto const it = tblDBNumToNatNum.find(eLang);
170 if (it != tblDBNumToNatNum.end())
171 nNatNum = it->second[nDBNum - 1];
175 return nNatNum;
178 const std::map<LanguageType, std::array<sal_uInt8, 9>> tblNatNumToDBNum
179 = { { primary(LANGUAGE_CHINESE), { 1, 0, 0, 1, 2, 3, 0, 0, 0 } },
180 { primary(LANGUAGE_JAPANESE), { 1, 2, 3, 1, 2, 3, 1, 2, 0 } },
181 { primary(LANGUAGE_KOREAN), { 1, 2, 3, 1, 2, 3, 1, 2, 4 } } };
183 // static
184 sal_uInt8 SvNumberNatNum::MapNatNumToDBNum( sal_uInt8 nNatNum, LanguageType eLang, bool bDate )
186 sal_uInt8 nDBNum = 0;
187 eLang = MsLangId::getRealLanguage( eLang ); // resolve SYSTEM etc.
188 eLang = primary(eLang); // 10 bit primary language
189 if ( bDate )
191 if ( nNatNum == 10 && eLang == primary(LANGUAGE_KOREAN) )
193 nDBNum = 4;
195 else if ( nNatNum <= 3 )
197 nDBNum = nNatNum; // known to be good for: zh,ja,ko / 1,2,3
200 else
202 if (1 <= nNatNum && nNatNum <= 9)
204 auto const it = tblNatNumToDBNum.find(eLang);
205 if (it != tblNatNumToDBNum.end())
206 nDBNum = it->second[nNatNum - 1];
209 return nDBNum;
213 * SvNumFor
216 ImpSvNumFor::ImpSvNumFor()
218 nStringsCnt = 0;
219 aI.eScannedType = SvNumFormatType::UNDEFINED;
220 aI.bThousand = false;
221 aI.nThousand = 0;
222 aI.nCntPre = 0;
223 aI.nCntPost = 0;
224 aI.nCntExp = 0;
225 pColor = nullptr;
228 ImpSvNumFor::~ImpSvNumFor()
232 void ImpSvNumFor::Enlarge(sal_uInt16 nCnt)
234 if ( nStringsCnt != nCnt )
236 nStringsCnt = nCnt;
237 aI.nTypeArray.resize(nCnt);
238 aI.sStrArray.resize(nCnt);
242 void ImpSvNumFor::Copy( const ImpSvNumFor& rNumFor, const ImpSvNumberformatScan* pSc )
244 Enlarge( rNumFor.nStringsCnt );
245 aI.Copy( rNumFor.aI, nStringsCnt );
246 sColorName = rNumFor.sColorName;
247 if ( pSc )
249 pColor = pSc->GetColor( sColorName ); // #121103# don't copy pointer between documents
251 else
253 pColor = rNumFor.pColor;
255 aNatNum = rNumFor.aNatNum;
258 bool ImpSvNumFor::HasNewCurrency() const
260 for ( sal_uInt16 j=0; j<nStringsCnt; j++ )
262 if ( aI.nTypeArray[j] == NF_SYMBOLTYPE_CURRENCY )
264 return true;
267 return false;
270 bool ImpSvNumFor::GetNewCurrencySymbol( OUString& rSymbol,
271 OUString& rExtension ) const
273 for ( sal_uInt16 j=0; j<nStringsCnt; j++ )
275 if ( aI.nTypeArray[j] == NF_SYMBOLTYPE_CURRENCY )
277 rSymbol = aI.sStrArray[j];
278 if ( j < nStringsCnt-1 && aI.nTypeArray[j+1] == NF_SYMBOLTYPE_CURREXT )
280 rExtension = aI.sStrArray[j+1];
282 else
284 rExtension.clear();
286 return true;
289 //! No Erase at rSymbol, rExtension
290 return false;
294 * SvNumberformat
297 namespace {
299 enum BracketFormatSymbolType
301 BRACKET_SYMBOLTYPE_FORMAT = -1, // subformat string
302 BRACKET_SYMBOLTYPE_COLOR = -2, // color
303 BRACKET_SYMBOLTYPE_ERROR = -3, // error
304 BRACKET_SYMBOLTYPE_DBNUM1 = -4, // DoubleByteNumber, represent numbers
305 BRACKET_SYMBOLTYPE_DBNUM2 = -5, // using CJK characters, Excel compatible
306 BRACKET_SYMBOLTYPE_DBNUM3 = -6,
307 BRACKET_SYMBOLTYPE_DBNUM4 = -7,
308 BRACKET_SYMBOLTYPE_DBNUM5 = -8,
309 BRACKET_SYMBOLTYPE_DBNUM6 = -9,
310 BRACKET_SYMBOLTYPE_DBNUM7 = -10,
311 BRACKET_SYMBOLTYPE_DBNUM8 = -11,
312 BRACKET_SYMBOLTYPE_DBNUM9 = -12,
313 BRACKET_SYMBOLTYPE_LOCALE = -13,
314 BRACKET_SYMBOLTYPE_NATNUM0 = -14, // Our NativeNumber support, ASCII
315 BRACKET_SYMBOLTYPE_NATNUM1 = -15, // Our NativeNumber support, represent
316 BRACKET_SYMBOLTYPE_NATNUM2 = -16, // numbers using CJK, CTL, ...
317 BRACKET_SYMBOLTYPE_NATNUM3 = -17,
318 BRACKET_SYMBOLTYPE_NATNUM4 = -18,
319 BRACKET_SYMBOLTYPE_NATNUM5 = -19,
320 BRACKET_SYMBOLTYPE_NATNUM6 = -20,
321 BRACKET_SYMBOLTYPE_NATNUM7 = -21,
322 BRACKET_SYMBOLTYPE_NATNUM8 = -22,
323 BRACKET_SYMBOLTYPE_NATNUM9 = -23,
324 BRACKET_SYMBOLTYPE_NATNUM10 = -24,
325 BRACKET_SYMBOLTYPE_NATNUM11 = -25,
326 BRACKET_SYMBOLTYPE_NATNUM12 = -26,
327 BRACKET_SYMBOLTYPE_NATNUM13 = -27,
328 BRACKET_SYMBOLTYPE_NATNUM14 = -28,
329 BRACKET_SYMBOLTYPE_NATNUM15 = -29,
330 BRACKET_SYMBOLTYPE_NATNUM16 = -30,
331 BRACKET_SYMBOLTYPE_NATNUM17 = -31,
332 BRACKET_SYMBOLTYPE_NATNUM18 = -32,
333 BRACKET_SYMBOLTYPE_NATNUM19 = -33
338 void SvNumberformat::ImpCopyNumberformat( const SvNumberformat& rFormat )
340 sFormatstring = rFormat.sFormatstring;
341 eType = rFormat.eType;
342 maLocale = rFormat.maLocale;
343 fLimit1 = rFormat.fLimit1;
344 fLimit2 = rFormat.fLimit2;
345 eOp1 = rFormat.eOp1;
346 eOp2 = rFormat.eOp2;
347 bStandard = rFormat.bStandard;
348 bIsUsed = rFormat.bIsUsed;
349 sComment = rFormat.sComment;
350 bAdditionalBuiltin = rFormat.bAdditionalBuiltin;
352 // #121103# when copying between documents, get color pointers from own scanner
353 ImpSvNumberformatScan* pColorSc = ( &rScan != &rFormat.rScan ) ? &rScan : nullptr;
355 for (sal_uInt16 i = 0; i < 4; i++)
357 NumFor[i].Copy(rFormat.NumFor[i], pColorSc);
361 SvNumberformat::SvNumberformat( SvNumberformat const & rFormat )
362 : rScan(rFormat.rScan), bStarFlag( rFormat.bStarFlag )
364 ImpCopyNumberformat( rFormat );
367 SvNumberformat::SvNumberformat( SvNumberformat const & rFormat, ImpSvNumberformatScan& rSc )
368 : rScan(rSc)
369 , bStarFlag( rFormat.bStarFlag )
371 ImpCopyNumberformat( rFormat );
374 static bool lcl_SvNumberformat_IsBracketedPrefix( short nSymbolType )
376 if ( nSymbolType > 0 )
378 return true; // conditions
380 switch ( nSymbolType )
382 case BRACKET_SYMBOLTYPE_COLOR :
383 case BRACKET_SYMBOLTYPE_DBNUM1 :
384 case BRACKET_SYMBOLTYPE_DBNUM2 :
385 case BRACKET_SYMBOLTYPE_DBNUM3 :
386 case BRACKET_SYMBOLTYPE_DBNUM4 :
387 case BRACKET_SYMBOLTYPE_DBNUM5 :
388 case BRACKET_SYMBOLTYPE_DBNUM6 :
389 case BRACKET_SYMBOLTYPE_DBNUM7 :
390 case BRACKET_SYMBOLTYPE_DBNUM8 :
391 case BRACKET_SYMBOLTYPE_DBNUM9 :
392 case BRACKET_SYMBOLTYPE_LOCALE :
393 case BRACKET_SYMBOLTYPE_NATNUM0 :
394 case BRACKET_SYMBOLTYPE_NATNUM1 :
395 case BRACKET_SYMBOLTYPE_NATNUM2 :
396 case BRACKET_SYMBOLTYPE_NATNUM3 :
397 case BRACKET_SYMBOLTYPE_NATNUM4 :
398 case BRACKET_SYMBOLTYPE_NATNUM5 :
399 case BRACKET_SYMBOLTYPE_NATNUM6 :
400 case BRACKET_SYMBOLTYPE_NATNUM7 :
401 case BRACKET_SYMBOLTYPE_NATNUM8 :
402 case BRACKET_SYMBOLTYPE_NATNUM9 :
403 case BRACKET_SYMBOLTYPE_NATNUM10 :
404 case BRACKET_SYMBOLTYPE_NATNUM11 :
405 case BRACKET_SYMBOLTYPE_NATNUM12 :
406 case BRACKET_SYMBOLTYPE_NATNUM13 :
407 case BRACKET_SYMBOLTYPE_NATNUM14 :
408 case BRACKET_SYMBOLTYPE_NATNUM15 :
409 case BRACKET_SYMBOLTYPE_NATNUM16 :
410 case BRACKET_SYMBOLTYPE_NATNUM17 :
411 case BRACKET_SYMBOLTYPE_NATNUM18 :
412 case BRACKET_SYMBOLTYPE_NATNUM19 :
413 return true;
415 return false;
418 /** Import extended LCID from Excel
420 OUString SvNumberformat::ImpObtainCalendarAndNumerals( OUStringBuffer& rString, sal_Int32 nPos,
421 LanguageType& nLang, const LocaleType& aTmpLocale )
423 OUString sCalendar;
424 sal_uInt16 nNatNum = 0;
425 LanguageType nLocaleLang = MsLangId::getRealLanguage( maLocale.meLanguage );
426 LanguageType nTmpLocaleLang = MsLangId::getRealLanguage( aTmpLocale.meLanguage );
427 /* NOTE: enhancement to allow other possible locale dependent
428 * calendars and numerals. BUT only if our locale data allows it! For LCID
429 * numerals and calendars see
430 * http://office.microsoft.com/en-us/excel/HA010346351033.aspx
431 * Calendar is inserted after
432 * all prefixes have been consumed as it is actually a format modifier
433 * and not a prefix.
434 * Currently calendars are tied to the locale of the entire number
435 * format, e.g. [~buddhist] in en_US doesn't work.
436 * => Having different locales in sub formats does not work!
437 * */
438 /* TODO: calendars could be tied to a sub format's NatNum info
439 * instead, or even better be available for any locale. Needs a
440 * different implementation of GetCal() and locale data calendars.
441 * */
442 switch ( aTmpLocale.mnCalendarType & 0x7F )
444 case 0x03 : // Gengou calendar
445 // Only Japanese language support Gengou calendar.
446 // It is an implicit "other" calendar where E, EE, R and RR
447 // automatically switch to and YY and YYYY switch to Gregorian. Do
448 // not add the "[~gengou]" modifier.
449 if ( nLocaleLang != LANGUAGE_JAPANESE )
451 nLang = maLocale.meLanguage = LANGUAGE_JAPANESE;
453 break;
454 case 0x05 : // Korean Dangi calendar
455 sCalendar = "[~dangi]";
456 // Only Korean language support dangi calendar
457 if ( nLocaleLang != LANGUAGE_KOREAN )
459 nLang = maLocale.meLanguage = LANGUAGE_KOREAN;
461 break;
462 case 0x06 : // Hijri calendar
463 case 0x17 : // same?
464 sCalendar = "[~hijri]";
465 // Only Arabic or Farsi languages support Hijri calendar
466 if ( ( primary( nLocaleLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY )
467 && nLocaleLang != LANGUAGE_FARSI )
469 if ( ( primary( nTmpLocaleLang ) == LANGUAGE_ARABIC_PRIMARY_ONLY )
470 || nTmpLocaleLang == LANGUAGE_FARSI )
472 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
474 else
476 nLang = maLocale.meLanguage = LANGUAGE_ARABIC_SAUDI_ARABIA;
479 break;
480 case 0x07 : // Buddhist calendar
481 sCalendar="[~buddhist]";
482 // Only Thai or Lao languages support Buddhist calendar
483 if ( nLocaleLang != LANGUAGE_THAI && nLocaleLang != LANGUAGE_LAO )
485 if ( nTmpLocaleLang == LANGUAGE_THAI || nTmpLocaleLang == LANGUAGE_LAO )
487 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
489 else
491 nLang = maLocale.meLanguage = LANGUAGE_THAI;
494 break;
495 case 0x08 : // Hebrew calendar
496 sCalendar = "[~jewish]";
497 // Many languages (but not all) support Jewish calendar
498 // Unable to find any logic => keep same language
499 break;
500 case 0x0E : // unknown calendar
501 case 0x0F : // unknown calendar
502 case 0x10 : // Indian calendar (unsupported)
503 case 0x11 : // unknown calendar
504 case 0x12 : // unknown calendar
505 case 0x13 : // unknown calendar
506 default : // other calendars (see tdf#36038) are not handle by LibO
507 break;
509 /** Reference language for each numeral ID */
510 static const LanguageType aNumeralIDtoLanguage []=
512 LANGUAGE_DONTKNOW, // 0x00
513 LANGUAGE_ENGLISH_US, // 0x01
514 LANGUAGE_ARABIC_SAUDI_ARABIA, // 0x02 + all Arabic
515 LANGUAGE_FARSI, // 0x03
516 LANGUAGE_HINDI, // 0x04 + Devanagari
517 LANGUAGE_BENGALI, // 0x05
518 LANGUAGE_PUNJABI, // 0x06
519 LANGUAGE_GUJARATI, // 0x07
520 LANGUAGE_ODIA, // 0x08
521 LANGUAGE_TAMIL, // 0x09
522 LANGUAGE_TELUGU, // 0x0A
523 LANGUAGE_KANNADA, // 0x0B
524 LANGUAGE_MALAYALAM, // 0x0C
525 LANGUAGE_THAI, // 0x0D
526 LANGUAGE_LAO, // 0x0E
527 LANGUAGE_TIBETAN, // 0x0F
528 LANGUAGE_BURMESE, // 0x10
529 LANGUAGE_TIGRIGNA_ETHIOPIA, // 0x11
530 LANGUAGE_KHMER, // 0x12
531 LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA, // 0x13
532 LANGUAGE_DONTKNOW, // 0x14
533 LANGUAGE_DONTKNOW, // 0x15
534 LANGUAGE_DONTKNOW, // 0x16
535 LANGUAGE_DONTKNOW, // 0x17
536 LANGUAGE_DONTKNOW, // 0x18
537 LANGUAGE_DONTKNOW, // 0x19
538 LANGUAGE_DONTKNOW, // 0x1A
539 LANGUAGE_JAPANESE, // 0x1B
540 LANGUAGE_JAPANESE, // 0x1C
541 LANGUAGE_JAPANESE, // 0x1D
542 LANGUAGE_CHINESE_SIMPLIFIED, // 0x1E
543 LANGUAGE_CHINESE_SIMPLIFIED, // 0x1F
544 LANGUAGE_CHINESE_SIMPLIFIED, // 0x20
545 LANGUAGE_CHINESE_TRADITIONAL, // 0x21
546 LANGUAGE_CHINESE_TRADITIONAL, // 0x22
547 LANGUAGE_CHINESE_TRADITIONAL, // 0x23
548 LANGUAGE_KOREAN, // 0x24
549 LANGUAGE_KOREAN, // 0x25
550 LANGUAGE_KOREAN, // 0x26
551 LANGUAGE_KOREAN // 0x27
554 sal_uInt16 nNumeralID = aTmpLocale.mnNumeralShape & 0x7F;
555 LanguageType nReferenceLanguage = nNumeralID <= 0x27 ? aNumeralIDtoLanguage[nNumeralID] : LANGUAGE_DONTKNOW;
557 switch ( nNumeralID )
559 // Regular cases: all languages with same primary mask use same numerals
560 case 0x03 : // Perso-Arabic (Farsi) numerals
561 case 0x05 : // Bengali numerals
562 case 0x06 : // Punjabi numerals
563 case 0x07 : // Gujarati numerals
564 case 0x08 : // Odia (Orya) numerals
565 case 0x09 : // Tamil numerals
566 case 0x0A : // Telugu numerals
567 case 0x0B : // Kannada numerals
568 case 0x0C : // Malayalam numerals
569 case 0x0D : // Thai numerals
570 case 0x0E : // Lao numerals
571 case 0x0F : // Tibetan numerals
572 case 0x10 : // Burmese (Myanmar) numerals
573 case 0x11 : // Tigrigna (Ethiopia) numerals
574 case 0x12 : // Khmer numerals
575 if ( primary( nLocaleLang ) != primary( nReferenceLanguage ) )
577 if ( primary( nTmpLocaleLang ) == primary( nReferenceLanguage ) )
579 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
581 else
583 nLang = maLocale.meLanguage = nReferenceLanguage;
586 break;
587 // Special cases
588 case 0x04 : // Devanagari (Hindi) numerals
589 // same numerals (Devanagari) for languages with different primary masks
590 if ( nLocaleLang != LANGUAGE_HINDI && nLocaleLang != LANGUAGE_MARATHI
591 && primary( nLocaleLang ) != primary( LANGUAGE_NEPALI ) )
593 if ( nTmpLocaleLang == LANGUAGE_HINDI || nTmpLocaleLang == LANGUAGE_MARATHI
594 || primary( nTmpLocaleLang ) == primary( LANGUAGE_NEPALI ) )
596 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
598 else
600 nLang = maLocale.meLanguage = LANGUAGE_HINDI;
603 break;
604 case 0x13 : // Mongolian numerals
605 // not all Mongolian languages use Mongolian numerals
606 if ( nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA
607 && nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA
608 && nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_LSO )
610 if ( nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA
611 || nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA
612 || nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_LSO )
614 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
616 else
618 nLang = maLocale.meLanguage = LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA;
621 break;
622 case 0x02 : // Eastern-Arabic numerals
623 // all arabic primary mask + LANGUAGE_PUNJABI_ARABIC_LSO
624 if ( primary( nLocaleLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY
625 && nLocaleLang != LANGUAGE_PUNJABI_ARABIC_LSO )
627 if ( primary( nTmpLocaleLang ) == LANGUAGE_ARABIC_PRIMARY_ONLY
628 || nTmpLocaleLang != LANGUAGE_PUNJABI_ARABIC_LSO )
630 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
632 else
634 nLang = maLocale.meLanguage = nReferenceLanguage;
637 break;
638 // CJK numerals
639 case 0x1B : // simple Asian numerals, Japanese
640 case 0x1C : // financial Asian numerals, Japanese
641 case 0x1D : // Arabic fullwidth numerals, Japanese
642 case 0x24 : // simple Asian numerals, Korean
643 case 0x25 : // financial Asian numerals, Korean
644 case 0x26 : // Arabic fullwidth numerals, Korean
645 case 0x27 : // Korean Hangul numerals
646 // Japanese and Korean are regular
647 if ( primary( nLocaleLang ) != primary( nReferenceLanguage ) )
649 if ( primary( nTmpLocaleLang ) == primary( nReferenceLanguage ) )
651 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
653 else
655 nLang = maLocale.meLanguage = nReferenceLanguage;
658 [[fallthrough]];
659 case 0x1E : // simple Asian numerals, Chinese-PRC
660 case 0x1F : // financial Asian numerals, Chinese-PRC
661 case 0x20 : // Arabic fullwidth numerals, Chinese-PRC
662 case 0x21 : // simple Asian numerals, Chinese-Taiwan
663 case 0x22 : // financial Asian numerals, Chinese-Taiwan
664 case 0x23 : // Arabic fullwidth numerals, Chinese-Taiwan
665 nNatNum = nNumeralID == 0x27 ? 9 : ( ( nNumeralID - 0x1B ) % 3 ) + 1;
666 // [NatNum1] simple numerals
667 // [natNum2] financial numerals
668 // [NatNum3] Arabic fullwidth numerals
669 // Chinese simplified and Chinese traditional have same primary mask
670 // Chinese-PRC
671 if ( nReferenceLanguage == LANGUAGE_CHINESE_SIMPLIFIED
672 && nLocaleLang != LANGUAGE_CHINESE_SIMPLIFIED
673 && nLocaleLang != LANGUAGE_CHINESE_SINGAPORE
674 && nLocaleLang != LANGUAGE_CHINESE_LSO )
676 if ( nTmpLocaleLang == LANGUAGE_CHINESE_SIMPLIFIED
677 || nTmpLocaleLang == LANGUAGE_CHINESE_SINGAPORE
678 || nTmpLocaleLang == LANGUAGE_CHINESE_LSO )
680 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
682 else
684 nLang = maLocale.meLanguage = LANGUAGE_CHINESE_SIMPLIFIED;
687 // Chinese-Taiwan
688 else if ( nReferenceLanguage == LANGUAGE_CHINESE_TRADITIONAL
689 && nLocaleLang != LANGUAGE_CHINESE_TRADITIONAL
690 && nLocaleLang != LANGUAGE_CHINESE_HONGKONG
691 && nLocaleLang != LANGUAGE_CHINESE_MACAU )
693 if ( nTmpLocaleLang == LANGUAGE_CHINESE_TRADITIONAL
694 || nTmpLocaleLang == LANGUAGE_CHINESE_HONGKONG
695 || nTmpLocaleLang == LANGUAGE_CHINESE_MACAU )
697 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
699 else
701 nLang = maLocale.meLanguage = LANGUAGE_CHINESE_TRADITIONAL;
704 break;
706 if ( nNumeralID >= 0x02 && nNumeralID <= 0x13 )
707 nNatNum = 1;
708 if ( nNatNum )
709 rString.insert( nPos, OUStringConcatenation("[NatNum"+OUString::number(nNatNum)+"]"));
710 return sCalendar;
713 namespace
715 bool NatNumTakesParameters(sal_Int16 nNum)
717 return (nNum == css::i18n::NativeNumberMode::NATNUM12);
721 SvNumberformat::SvNumberformat(OUString& rString,
722 ImpSvNumberformatScan* pSc,
723 ImpSvNumberInputScan* pISc,
724 sal_Int32& nCheckPos,
725 LanguageType& eLan,
726 bool bReplaceBooleanEquivalent)
727 : rScan(*pSc)
728 , bAdditionalBuiltin( false )
729 , bStarFlag( false )
731 if (bReplaceBooleanEquivalent)
732 rScan.ReplaceBooleanEquivalent( rString);
734 OUStringBuffer sBuff(rString);
736 // If the group (AKA thousand) separator is a No-Break Space (French)
737 // replace all occurrences by a simple space.
738 // The same for Narrow No-Break Space just in case some locale uses it.
739 // The tokens will be changed to the LocaleData separator again later on.
740 const OUString& rThSep = GetFormatter().GetNumThousandSep();
741 if ( rThSep.getLength() == 1)
743 const sal_Unicode cNBSp = 0xA0;
744 const sal_Unicode cNNBSp = 0x202F;
745 if (rThSep[0] == cNBSp )
746 sBuff.replace( cNBSp, ' ');
747 else if (rThSep[0] == cNNBSp )
748 sBuff.replace( cNNBSp, ' ');
751 OUString aConvertFromDecSep;
752 OUString aConvertToDecSep;
753 if (rScan.GetConvertMode())
755 aConvertFromDecSep = GetFormatter().GetNumDecimalSep();
756 maLocale.meLanguage = rScan.GetNewLnge();
757 eLan = maLocale.meLanguage; // Make sure to return switch
759 else
761 maLocale.meLanguage = eLan;
763 bStandard = false;
764 bIsUsed = false;
765 fLimit1 = 0.0;
766 fLimit2 = 0.0;
767 eOp1 = NUMBERFORMAT_OP_NO;
768 eOp2 = NUMBERFORMAT_OP_NO;
769 eType = SvNumFormatType::DEFINED;
771 bool bCancel = false;
772 bool bCondition = false;
773 short eSymbolType;
774 sal_Int32 nPos = 0;
775 sal_Int32 nPosOld;
776 nCheckPos = 0;
778 // Split into 4 sub formats
779 sal_uInt16 nIndex;
780 for ( nIndex = 0; nIndex < 4 && !bCancel; nIndex++ )
782 // Original language/country may have to be reestablished
783 if (rScan.GetConvertMode())
785 rScan.GetNumberformatter()->ChangeIntl(rScan.GetTmpLnge());
787 OUString sInsertCalendar; // a calendar resulting from parsing LCID
788 OUString sStr;
789 nPosOld = nPos; // Start position of substring
790 // first get bracketed prefixes; e.g. conditions, color
793 eSymbolType = ImpNextSymbol(sBuff, nPos, sStr);
794 if (eSymbolType > 0) // condition
796 if ( nIndex == 0 && !bCondition )
798 bCondition = true;
799 eOp1 = static_cast<SvNumberformatLimitOps>(eSymbolType);
801 else if ( nIndex == 1 && bCondition )
803 eOp2 = static_cast<SvNumberformatLimitOps>(eSymbolType);
805 else // error
807 bCancel = true; // break for
808 nCheckPos = nPosOld;
810 if (!bCancel)
812 double fNumber;
813 sal_Int32 nCntChars = ImpGetNumber(sBuff, nPos, sStr);
814 if (nCntChars > 0)
816 sal_Int32 nDecPos;
817 SvNumFormatType F_Type = SvNumFormatType::UNDEFINED;
818 if (!pISc->IsNumberFormat(sStr, F_Type, fNumber, nullptr, SvNumInputOptions::NONE) ||
819 ( F_Type != SvNumFormatType::NUMBER &&
820 F_Type != SvNumFormatType::SCIENTIFIC) )
822 fNumber = 0.0;
823 nPos = nPos - nCntChars;
824 sBuff.remove(nPos, nCntChars);
825 sBuff.insert(nPos, '0');
826 nPos++;
828 else if (rScan.GetConvertMode() && ((nDecPos = sStr.indexOf( aConvertFromDecSep)) >= 0))
830 if (aConvertToDecSep.isEmpty())
831 aConvertToDecSep = GetFormatter().GetLangDecimalSep( rScan.GetNewLnge());
832 if (aConvertToDecSep != aConvertFromDecSep)
834 const OUString aStr( sStr.replaceAt( nDecPos,
835 aConvertFromDecSep.getLength(), aConvertToDecSep));
836 nPos = nPos - nCntChars;
837 sBuff.remove(nPos, nCntChars);
838 sBuff.insert(nPos, aStr);
839 nPos += aStr.getLength();
843 else
845 fNumber = 0.0;
846 sBuff.insert(nPos++, '0');
848 if (nIndex == 0)
850 fLimit1 = fNumber;
852 else
854 fLimit2 = fNumber;
856 if ( nPos < sBuff.getLength() && sBuff[nPos] == ']' )
858 nPos++;
860 else
862 bCancel = true; // break for
863 nCheckPos = nPos;
866 nPosOld = nPos; // position before string
868 else if ( lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) )
870 OUString sSymbol( sStr);
871 switch ( eSymbolType )
873 case BRACKET_SYMBOLTYPE_COLOR :
874 if ( NumFor[nIndex].GetColor() != nullptr )
875 { // error, more than one color
876 bCancel = true; // break for
877 nCheckPos = nPosOld;
879 else
881 const Color* pColor = pSc->GetColor( sStr);
882 NumFor[nIndex].SetColor( pColor, sStr);
883 if (pColor == nullptr)
884 { // error
885 bCancel = true; // break for
886 nCheckPos = nPosOld;
889 break;
890 case BRACKET_SYMBOLTYPE_NATNUM0 :
891 case BRACKET_SYMBOLTYPE_NATNUM1 :
892 case BRACKET_SYMBOLTYPE_NATNUM2 :
893 case BRACKET_SYMBOLTYPE_NATNUM3 :
894 case BRACKET_SYMBOLTYPE_NATNUM4 :
895 case BRACKET_SYMBOLTYPE_NATNUM5 :
896 case BRACKET_SYMBOLTYPE_NATNUM6 :
897 case BRACKET_SYMBOLTYPE_NATNUM7 :
898 case BRACKET_SYMBOLTYPE_NATNUM8 :
899 case BRACKET_SYMBOLTYPE_NATNUM9 :
900 case BRACKET_SYMBOLTYPE_NATNUM10 :
901 case BRACKET_SYMBOLTYPE_NATNUM11 :
902 case BRACKET_SYMBOLTYPE_NATNUM12 :
903 case BRACKET_SYMBOLTYPE_NATNUM13 :
904 case BRACKET_SYMBOLTYPE_NATNUM14 :
905 case BRACKET_SYMBOLTYPE_NATNUM15 :
906 case BRACKET_SYMBOLTYPE_NATNUM16 :
907 case BRACKET_SYMBOLTYPE_NATNUM17 :
908 case BRACKET_SYMBOLTYPE_NATNUM18 :
909 case BRACKET_SYMBOLTYPE_NATNUM19 :
910 if ( NumFor[nIndex].GetNatNum().IsSet() )
912 bCancel = true; // break for
913 nCheckPos = nPosOld;
915 else
917 OUString sParams;
918 sal_Int32 nSpacePos = sStr.indexOf(' ');
919 if (nSpacePos >= 0)
921 sParams = sStr.copy(nSpacePos+1).trim();
923 //! eSymbolType is negative
924 sal_uInt8 nNum = static_cast<sal_uInt8>(0 - (eSymbolType - BRACKET_SYMBOLTYPE_NATNUM0));
925 if (!sParams.isEmpty() && !NatNumTakesParameters(nNum))
927 bCancel = true; // break for
928 nCheckPos = nPosOld;
929 break;
931 sStr = "NatNum" + OUString::number(nNum);
932 NumFor[nIndex].SetNatNumNum( nNum, false );
933 // NatNum12 supports arguments
934 if (nNum == 12)
936 if (sParams.isEmpty())
937 sParams = "cardinal"; // default NatNum12 format is "cardinal"
938 NumFor[nIndex].SetNatNumParams(sParams);
939 sStr += " " + sParams;
942 break;
943 case BRACKET_SYMBOLTYPE_DBNUM1 :
944 case BRACKET_SYMBOLTYPE_DBNUM2 :
945 case BRACKET_SYMBOLTYPE_DBNUM3 :
946 case BRACKET_SYMBOLTYPE_DBNUM4 :
947 case BRACKET_SYMBOLTYPE_DBNUM5 :
948 case BRACKET_SYMBOLTYPE_DBNUM6 :
949 case BRACKET_SYMBOLTYPE_DBNUM7 :
950 case BRACKET_SYMBOLTYPE_DBNUM8 :
951 case BRACKET_SYMBOLTYPE_DBNUM9 :
952 if ( NumFor[nIndex].GetNatNum().IsSet() )
954 bCancel = true; // break for
955 nCheckPos = nPosOld;
957 else
959 //! eSymbolType is negative
960 sal_uInt8 nNum = static_cast<sal_uInt8>(1 - (eSymbolType - BRACKET_SYMBOLTYPE_DBNUM1));
961 sStr = "DBNum" + OUStringChar(sal_Unicode('0' + nNum));
962 NumFor[nIndex].SetNatNumNum( nNum, true );
964 break;
965 case BRACKET_SYMBOLTYPE_LOCALE :
966 if ( NumFor[nIndex].GetNatNum().GetLang() != LANGUAGE_DONTKNOW ||
967 sBuff[nPos-1] != ']' )
968 // Check also for ']' to avoid pulling in
969 // locale data for the preview string for not
970 // yet completed LCIDs in the dialog.
972 bCancel = true; // break for
973 nCheckPos = nPosOld;
975 else
977 sal_Int32 nTmp = 2;
978 LocaleType aTmpLocale( ImpGetLocaleType( sStr, nTmp));
979 if (aTmpLocale.meLanguage == LANGUAGE_DONTKNOW)
981 bCancel = true; // break for
982 nCheckPos = nPosOld;
984 else
986 // Only the first sub format's locale will be
987 // used as the format's overall locale.
988 // Sorts this also under the corresponding
989 // locale for the dialog.
990 // If we don't support the locale this would
991 // result in an unknown (empty) language
992 // listbox entry and the user would never see
993 // this format.
994 if (nIndex == 0 && (aTmpLocale.meLanguage == LANGUAGE_SYSTEM ||
995 SvNumberFormatter::IsLocaleInstalled( aTmpLocale.meLanguage)))
997 maLocale = aTmpLocale;
998 eLan = aTmpLocale.meLanguage; // return to caller
1000 // Set new target locale also at scanner.
1001 // We have to do this because switching locale
1002 // may make replacing keywords and separators
1003 // necessary.
1004 // We can do this because it's the first
1005 // subformat and we're still at parsing the
1006 // modifiers, not keywords.
1007 rScan.SetNewLnge( eLan);
1008 // We can not force conversion though because
1009 // the caller may have explicitly not set it.
1010 // In the usual case the target locale is the
1011 // originating locale the conversion is not
1012 // necessary, when reading alien documents
1013 // conversion is enabled anyway.
1015 /* TODO: fiddle with scanner to make this
1016 * known? A change in the locale may affect
1017 * separators and keywords. On the other
1018 * hand they may have been entered as used
1019 * in the originating locale, there's no
1020 * way to predict other than analyzing the
1021 * format code, we assume here the current
1022 * context is used, which is most likely
1023 * the case.
1024 * */
1026 // Strip a plain locale identifier if locale
1027 // data is available to avoid duplicated
1028 // formats with and without LCID for the same
1029 // locale. Besides it looks ugly and confusing
1030 // and is unnecessary as the format will be
1031 // listed for the resulting locale.
1032 if (aTmpLocale.isPlainLocale())
1033 sStr.clear();
1034 else
1035 sStr = "$-" + aTmpLocale.generateCode();
1037 else
1039 if (nIndex == 0)
1040 // Locale data not available, remember.
1041 maLocale.meLanguageWithoutLocaleData = aTmpLocale.meLanguage;
1043 sStr = "$-" + aTmpLocale.generateCode();
1045 NumFor[nIndex].SetNatNumLang( MsLangId::getRealLanguage( aTmpLocale.meLanguage));
1047 // "$-NNCCLLLL" Numerals and Calendar
1048 if (sSymbol.getLength() > 6)
1050 sInsertCalendar = ImpObtainCalendarAndNumerals( sBuff, nPos, eLan, aTmpLocale);
1052 /* NOTE: there can be only one calendar
1053 * inserted so the last one wins, though
1054 * our own calendar modifiers support
1055 * multiple calendars within one sub format
1056 * code if at different positions. */
1059 break;
1061 if ( !bCancel )
1063 if (sStr == sSymbol)
1065 nPosOld = nPos;
1067 else
1069 sBuff.remove(nPosOld, nPos - nPosOld);
1070 if (!sStr.isEmpty())
1072 sBuff.insert(nPosOld, sStr);
1073 nPos = nPosOld + sStr.getLength();
1074 sBuff.insert(nPos, "]");
1075 sBuff.insert(nPosOld, "[");
1076 nPos += 2;
1077 nPosOld = nPos; // position before string
1079 else
1081 nPos = nPosOld; // prefix removed for whatever reason
1087 while ( !bCancel && lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) );
1089 // The remaining format code string
1090 if ( !bCancel )
1092 if (eSymbolType == BRACKET_SYMBOLTYPE_FORMAT)
1094 if (nIndex == 1 && eOp1 == NUMBERFORMAT_OP_NO)
1096 eOp1 = NUMBERFORMAT_OP_GT; // undefined condition, default: > 0
1098 else if (nIndex == 2 && eOp2 == NUMBERFORMAT_OP_NO)
1100 eOp2 = NUMBERFORMAT_OP_LT; // undefined condition, default: < 0
1102 if (sStr.isEmpty())
1104 // Empty sub format.
1105 NumFor[nIndex].Info().eScannedType = SvNumFormatType::EMPTY;
1107 else
1109 if (!sInsertCalendar.isEmpty())
1111 sStr = sInsertCalendar + sStr;
1113 sal_Int32 nStrPos = pSc->ScanFormat( sStr);
1114 sal_uInt16 nCnt = pSc->GetResultStringsCnt();
1115 if (nCnt == 0 && nStrPos == 0) // error
1117 nStrPos = 1;
1119 if (nStrPos == 0) // ok
1121 // e.g. Thai T speciality
1122 if (pSc->GetNatNumModifier() && !NumFor[nIndex].GetNatNum().IsSet())
1124 sStr = "[NatNum" + OUString::number( pSc->GetNatNumModifier()) + "]" + sStr;
1125 NumFor[nIndex].SetNatNumNum( pSc->GetNatNumModifier(), false );
1127 // #i53826# #i42727# For the Thai T speciality we need
1128 // to freeze the locale and immunize it against
1129 // conversions during exports, just in case we want to
1130 // save to Xcl. This disables the feature of being able
1131 // to convert a NatNum to another locale. You can't
1132 // have both.
1133 // FIXME: implement a specialized export conversion
1134 // that works on tokens (have to tokenize all first)
1135 // and doesn't use the format string and
1136 // PutandConvertEntry() to LANGUAGE_ENGLISH_US in
1137 // sc/source/filter/excel/xestyle.cxx
1138 // XclExpNumFmtBuffer::WriteFormatRecord().
1139 LanguageType eLanguage;
1140 if (NumFor[nIndex].GetNatNum().GetNatNum() == 1 &&
1141 ((eLanguage = MsLangId::getRealLanguage( eLan)) == LANGUAGE_THAI) &&
1142 NumFor[nIndex].GetNatNum().GetLang() == LANGUAGE_DONTKNOW)
1144 sStr = "[$-" + OUString::number( sal_uInt16(eLanguage), 16 ).toAsciiUpperCase() + "]" + sStr;
1145 NumFor[nIndex].SetNatNumLang( eLanguage);
1147 sBuff.remove(nPosOld, nPos - nPosOld);
1148 sBuff.insert(nPosOld, sStr);
1149 nPos = nPosOld + sStr.getLength();
1150 if (nPos < sBuff.getLength())
1152 sBuff.insert(nPos, ";");
1153 nPos++;
1155 else if (nIndex > 0)
1157 // The last subformat. If it is a trailing text
1158 // format the omitted subformats act like they were
1159 // not specified and "inherited" the first format,
1160 // e.g. 0;@ behaves like 0;-0;0;@
1161 if (pSc->GetScannedType() == SvNumFormatType::TEXT)
1163 // Reset conditions, reverting any set above.
1164 if (nIndex == 1)
1165 eOp1 = NUMBERFORMAT_OP_NO;
1166 else if (nIndex == 2)
1167 eOp2 = NUMBERFORMAT_OP_NO;
1168 nIndex = 3;
1171 NumFor[nIndex].Enlarge(nCnt);
1172 pSc->CopyInfo(&(NumFor[nIndex].Info()), nCnt);
1173 // type check
1174 if (nIndex == 0)
1176 eType = NumFor[nIndex].Info().eScannedType;
1178 else if (nIndex == 3)
1179 { // #77026# Everything recognized IS text
1180 NumFor[nIndex].Info().eScannedType = SvNumFormatType::TEXT;
1182 else if ( NumFor[nIndex].Info().eScannedType != eType)
1184 eType = SvNumFormatType::DEFINED;
1187 else
1189 nCheckPos = nPosOld + nStrPos; // error in string
1190 bCancel = true; // break for
1194 else if (eSymbolType == BRACKET_SYMBOLTYPE_ERROR) // error
1196 nCheckPos = nPosOld;
1197 bCancel = true;
1199 else if ( lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) )
1201 nCheckPos = nPosOld + 1; // error, prefix in string
1202 bCancel = true; // break for
1205 if ( bCancel && !nCheckPos )
1207 nCheckPos = 1; // nCheckPos is used as an error condition
1209 if ( !bCancel )
1211 if ( NumFor[nIndex].GetNatNum().IsSet() &&
1212 NumFor[nIndex].GetNatNum().GetLang() == LANGUAGE_DONTKNOW )
1214 NumFor[nIndex].SetNatNumLang( eLan );
1217 if (sBuff.getLength() == nPos)
1219 if (nIndex < 3 && rString[rString.getLength()-1] == ';')
1221 // A trailing ';' is significant and specifies the following
1222 // subformat to be empty. We don't enter the scanning loop
1223 // above again though.
1224 // Note that the operators apply to the current last scanned
1225 // subformat.
1226 if (nIndex == 0 && eOp1 == NUMBERFORMAT_OP_NO)
1228 eOp1 = NUMBERFORMAT_OP_GT; // undefined condition, default: > 0
1230 else if (nIndex == 1 && eOp2 == NUMBERFORMAT_OP_NO)
1232 eOp2 = NUMBERFORMAT_OP_LT; // undefined condition, default: < 0
1234 NumFor[nIndex+1].Info().eScannedType = SvNumFormatType::EMPTY;
1235 if (sBuff[nPos-1] != ';')
1236 sBuff.insert( nPos++, ';');
1238 if (nIndex == 2 && eSymbolType == BRACKET_SYMBOLTYPE_FORMAT && sBuff[nPos-1] == ';')
1240 // #83510# A 4th subformat explicitly specified to be empty
1241 // hides any text. Need the type here for HasTextFormat()
1242 NumFor[3].Info().eScannedType = SvNumFormatType::TEXT;
1244 bCancel = true;
1246 if ( NumFor[nIndex].GetNatNum().IsSet() )
1248 NumFor[nIndex].SetNatNumDate( bool(NumFor[nIndex].Info().eScannedType & SvNumFormatType::DATE) );
1252 if (!nCheckPos && IsSubstituted())
1254 // For to be substituted formats the scanned type must match the
1255 // substitute type.
1256 if (IsSystemTimeFormat())
1258 if ((eType & ~SvNumFormatType::DEFINED) != SvNumFormatType::TIME)
1259 nCheckPos = std::max<sal_Int32>( sBuff.indexOf(']') + 1, 1);
1261 else if (IsSystemLongDateFormat())
1263 if ((eType & ~SvNumFormatType::DEFINED) != SvNumFormatType::DATE)
1264 nCheckPos = std::max<sal_Int32>( sBuff.indexOf(']') + 1, 1);
1266 else
1267 assert(!"unhandled substitute");
1270 if ( bCondition && !nCheckPos )
1272 if ( nIndex == 1 && NumFor[0].GetCount() == 0 &&
1273 sBuff[sBuff.getLength() - 1] != ';' )
1275 // No format code => GENERAL but not if specified empty
1276 OUString aAdd( pSc->GetStandardName() );
1277 if ( !pSc->ScanFormat( aAdd ) )
1279 sal_uInt16 nCnt = pSc->GetResultStringsCnt();
1280 if ( nCnt )
1282 NumFor[0].Enlarge(nCnt);
1283 pSc->CopyInfo( &(NumFor[0].Info()), nCnt );
1284 sBuff.append(aAdd);
1288 else if ( nIndex == 1 && NumFor[nIndex].GetCount() == 0 &&
1289 sBuff[sBuff.getLength() - 1] != ';' &&
1290 (NumFor[0].GetCount() > 1 ||
1291 (NumFor[0].GetCount() == 1 &&
1292 NumFor[0].Info().nTypeArray[0] != NF_KEY_GENERAL)) )
1294 // No trailing second subformat => GENERAL but not if specified empty
1295 // and not if first subformat is GENERAL
1296 OUString aAdd( pSc->GetStandardName() );
1297 if ( !pSc->ScanFormat( aAdd ) )
1299 sal_uInt16 nCnt = pSc->GetResultStringsCnt();
1300 if ( nCnt )
1302 NumFor[nIndex].Enlarge(nCnt);
1303 pSc->CopyInfo( &(NumFor[nIndex].Info()), nCnt );
1304 sBuff.append(";");
1305 sBuff.append(aAdd);
1309 else if ( nIndex == 2 && NumFor[nIndex].GetCount() == 0 &&
1310 sBuff[sBuff.getLength() - 1] != ';' &&
1311 eOp2 != NUMBERFORMAT_OP_NO )
1313 // No trailing third subformat => GENERAL but not if specified empty
1314 OUString aAdd( pSc->GetStandardName() );
1315 if ( !pSc->ScanFormat( aAdd ) )
1317 sal_uInt16 nCnt = pSc->GetResultStringsCnt();
1318 if ( nCnt )
1320 NumFor[nIndex].Enlarge(nCnt);
1321 pSc->CopyInfo( &(NumFor[nIndex].Info()), nCnt );
1322 sBuff.append(";");
1323 sBuff.append(aAdd);
1328 rString = sBuff.makeStringAndClear();
1329 sFormatstring = rString;
1331 if (NumFor[2].GetCount() == 0 && // No third partial string
1332 eOp1 == NUMBERFORMAT_OP_GT && eOp2 == NUMBERFORMAT_OP_NO &&
1333 fLimit1 == 0.0 && fLimit2 == 0.0)
1335 eOp1 = NUMBERFORMAT_OP_GE; // Add 0 to the first format
1340 SvNumberformat::~SvNumberformat()
1345 * Next_Symbol
1347 * Splits up the symbols for further processing (by the Turing machine)
1349 * Start state = SsStart, * = Special state
1350 * ---------------+-------------------+----------------------------+---------------
1351 * Old State | Symbol read | Event | New state
1352 * ---------------+-------------------+----------------------------+---------------
1353 * SsStart | " | Symbol += Character | SsGetQuoted
1354 * | ; | Pos-- | SsGetString
1355 * | [ | Symbol += Character | SsGetBracketed
1356 * | ] | Error | SsStop
1357 * | BLANK | |
1358 * | Else | Symbol += Character | SsGetString
1359 * ---------------+-------------------+----------------------------+---------------
1360 * SsGetString | " | Symbol += Character | SsGetQuoted
1361 * | ; | | SsStop
1362 * | Else | Symbol += Character |
1363 * ---------------+-------------------+----------------------------+---------------
1364 * SsGetQuoted | " | Symbol += Character | SsGetString
1365 * | Else | Symbol += Character |
1366 * ---------------+-------------------+----------------------------+---------------
1367 * SsGetBracketed | <, > = | del [ |
1368 * | | Symbol += Character | SsGetCon
1369 * | BLANK | |
1370 * | h, H, m, M, s, S | Symbol += Character | SsGetTime
1371 * | Else | del [ |
1372 * | | Symbol += Character | SsGetPrefix
1373 * ---------------+-------------------+----------------------------+---------------
1374 * SsGetTime | ] | Symbol += Character | SsGetString
1375 * | h, H, m, M, s, S | Symbol += Character, * | SsGetString
1376 * | Else | del [; Symbol += Character | SsGetPrefix
1377 * ---------------+-------------------+----------------------------+---------------
1378 * SsGetPrefix | ] | | SsStop
1379 * | Else | Symbol += Character |
1380 * ---------------+-------------------+----------------------------+---------------
1381 * SsGetCon | >, = | Symbol += Character |
1382 * | ] | | SsStop
1383 * | Else | Error | SsStop
1384 * ---------------+-------------------+----------------------------+---------------
1387 namespace {
1389 enum ScanState
1391 SsStop,
1392 SsStart,
1393 SsGetCon, // condition
1394 SsGetString, // format string
1395 SsGetPrefix, // color or NatNumN
1396 SsGetTime, // [HH] for time
1397 SsGetBracketed, // any [...] not decided yet
1398 SsGetQuoted // quoted text
1403 // read a string until ']' and delete spaces in input
1404 // static
1405 sal_Int32 SvNumberformat::ImpGetNumber(OUStringBuffer& rString,
1406 sal_Int32& nPos,
1407 OUString& sSymbol)
1409 sal_Int32 nStartPos = nPos;
1410 sal_Unicode cToken;
1411 sal_Int32 nLen = rString.getLength();
1412 OUStringBuffer sBuffSymbol;
1413 while ( nPos < nLen )
1415 cToken = rString[nPos];
1416 if (cToken == ']')
1417 break;
1418 if (cToken == ' ')
1419 { // delete spaces
1420 rString.remove(nPos,1);
1421 nLen--;
1423 else
1425 nPos++;
1426 sBuffSymbol.append(cToken);
1429 sSymbol = sBuffSymbol.makeStringAndClear();
1430 return nPos - nStartPos;
1433 namespace {
1435 sal_Unicode toUniChar(sal_uInt8 n)
1437 char c;
1438 if (n < 10)
1440 c = '0' + n;
1442 else
1444 c = 'A' + n - 10;
1446 return sal_Unicode(c);
1449 bool IsCombiningSymbol( OUStringBuffer& rStringBuffer, sal_Int32 nPos )
1451 bool bRet = false;
1452 while (nPos >= 0)
1454 switch (rStringBuffer[nPos])
1456 case '*':
1457 case '\\':
1458 case '_':
1459 bRet = !bRet;
1460 --nPos;
1461 break;
1462 default:
1463 return bRet;
1466 return bRet;
1469 } // namespace
1471 OUString SvNumberformat::LocaleType::generateCode() const
1473 OUStringBuffer aBuf;
1474 #if 0
1475 // TODO: We may re-enable this later. Don't remove it! --Kohei
1476 if (mnNumeralShape)
1478 sal_uInt8 nVal = mnNumeralShape;
1479 for (sal_uInt8 i = 0; i < 2; ++i)
1481 sal_uInt8 n = (nVal & 0xF0) >> 4;
1482 if (n || aBuf.getLength())
1484 aBuf.append(toUniChar(n));
1486 nVal = nVal << 4;
1490 if (mnNumeralShape || mnCalendarType)
1492 sal_uInt8 nVal = mnCalendarType;
1493 for (sal_uInt8 i = 0; i < 2; ++i)
1495 sal_uInt8 n = (nVal & 0xF0) >> 4;
1496 if (n || aBuf.getLength())
1498 aBuf.append(toUniChar(n));
1500 nVal = nVal << 4;
1503 #endif
1505 sal_uInt16 n16 = static_cast<sal_uInt16>(
1506 (meLanguageWithoutLocaleData == LANGUAGE_DONTKNOW) ? meLanguage :
1507 meLanguageWithoutLocaleData);
1508 if (meLanguage == LANGUAGE_SYSTEM)
1510 switch (meSubstitute)
1512 case Substitute::NONE:
1513 ; // nothing
1514 break;
1515 case Substitute::TIME:
1516 n16 = static_cast<sal_uInt16>(LANGUAGE_NF_SYSTEM_TIME);
1517 break;
1518 case Substitute::LONGDATE:
1519 n16 = static_cast<sal_uInt16>(LANGUAGE_NF_SYSTEM_DATE);
1520 break;
1523 for (sal_uInt8 i = 0; i < 4; ++i)
1525 sal_uInt8 n = static_cast<sal_uInt8>((n16 & 0xF000) >> 12);
1526 // Omit leading zeros for consistency.
1527 if (n || !aBuf.isEmpty() || i == 3)
1529 aBuf.append(toUniChar(n));
1531 n16 = n16 << 4;
1534 return aBuf.makeStringAndClear();
1537 SvNumberformat::LocaleType::LocaleType()
1538 : meLanguage(LANGUAGE_DONTKNOW)
1539 , meLanguageWithoutLocaleData(LANGUAGE_DONTKNOW)
1540 , meSubstitute(Substitute::NONE)
1541 , mnNumeralShape(0)
1542 , mnCalendarType(0)
1546 SvNumberformat::LocaleType::LocaleType(sal_uInt32 nRawNum)
1547 : meLanguage(LANGUAGE_DONTKNOW)
1548 , meLanguageWithoutLocaleData(LANGUAGE_DONTKNOW)
1549 , meSubstitute(Substitute::NONE)
1550 , mnNumeralShape(0)
1551 , mnCalendarType(0)
1553 meLanguage = static_cast<LanguageType>(nRawNum & 0x0000FFFF);
1554 if (meLanguage == LANGUAGE_NF_SYSTEM_TIME)
1556 meSubstitute = Substitute::TIME;
1557 meLanguage = LANGUAGE_SYSTEM;
1559 else if (meLanguage == LANGUAGE_NF_SYSTEM_DATE)
1561 meSubstitute = Substitute::LONGDATE;
1562 meLanguage = LANGUAGE_SYSTEM;
1564 nRawNum = (nRawNum >> 16);
1565 mnCalendarType = static_cast<sal_uInt8>(nRawNum & 0xFF);
1566 nRawNum = (nRawNum >> 8);
1567 mnNumeralShape = static_cast<sal_uInt8>(nRawNum & 0xFF);
1570 bool SvNumberformat::LocaleType::isPlainLocale() const
1572 return meSubstitute == Substitute::NONE && !mnCalendarType && !mnNumeralShape;
1575 // static
1576 SvNumberformat::LocaleType SvNumberformat::ImpGetLocaleType(const OUString& rString, sal_Int32& nPos )
1578 sal_uInt32 nNum = 0;
1579 sal_Unicode cToken = 0;
1580 sal_Int32 nStart = nPos;
1581 sal_Int32 nLen = rString.getLength();
1582 while ( nPos < nLen && (nPos - nStart < 8) )
1584 cToken = rString[nPos];
1585 if (cToken == ']')
1586 break;
1587 if ( '0' <= cToken && cToken <= '9' )
1589 nNum *= 16;
1590 nNum += cToken - '0';
1592 else if ( 'a' <= cToken && cToken <= 'f' )
1594 nNum *= 16;
1595 nNum += cToken - 'a' + 10;
1597 else if ( 'A' <= cToken && cToken <= 'F' )
1599 nNum *= 16;
1600 nNum += cToken - 'A' + 10;
1602 else
1604 return LocaleType(); // LANGUAGE_DONTKNOW;
1606 ++nPos;
1609 return (cToken == ']' || nPos == nLen) ? LocaleType(nNum) : LocaleType();
1612 static bool lcl_matchKeywordAndGetNumber( const OUString & rString, const sal_Int32 nPos,
1613 const OUString & rKeyword, sal_Int32 & nNumber )
1615 if (0 <= nPos && nPos + rKeyword.getLength() < rString.getLength() && rString.matchIgnoreAsciiCase( rKeyword, nPos))
1617 nNumber = rString.copy( nPos + rKeyword.getLength()).toInt32();
1618 return true;
1620 else
1622 nNumber = 0;
1623 return false;
1627 short SvNumberformat::ImpNextSymbol(OUStringBuffer& rString,
1628 sal_Int32& nPos,
1629 OUString& sSymbol) const
1631 short eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
1632 sal_Unicode cToken;
1633 sal_Unicode cLetter = ' '; // Preliminary result
1634 sal_Int32 nLen = rString.getLength();
1635 ScanState eState = SsStart;
1636 OUStringBuffer sBuffSymbol(128);
1638 const NfKeywordTable & rKeywords = rScan.GetKeywords();
1639 while (nPos < nLen && eState != SsStop)
1641 cToken = rString[nPos];
1642 nPos++;
1643 switch (eState)
1645 case SsStart:
1646 if (cToken == '\"')
1648 eState = SsGetQuoted;
1649 sBuffSymbol.append(cToken);
1651 else if (cToken == '[')
1653 eState = SsGetBracketed;
1654 sBuffSymbol.append(cToken);
1656 else if (cToken == ';')
1658 eState = SsGetString;
1659 nPos--;
1660 eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
1662 else if (cToken == ']')
1664 eState = SsStop;
1665 eSymbolType = BRACKET_SYMBOLTYPE_ERROR;
1667 else if (cToken == ' ') // Skip Blanks
1669 nPos--;
1670 rString.remove(nPos, 1);
1671 nLen--;
1673 else
1675 sBuffSymbol.append(cToken);
1676 eState = SsGetString;
1677 eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
1679 break;
1680 case SsGetBracketed:
1681 switch (cToken)
1683 case '<':
1684 case '>':
1685 case '=':
1686 sBuffSymbol.stripStart('[');
1687 sBuffSymbol.append(cToken);
1688 cLetter = cToken;
1689 eState = SsGetCon;
1690 switch (cToken)
1692 case '<':
1693 eSymbolType = NUMBERFORMAT_OP_LT;
1694 break;
1695 case '>':
1696 eSymbolType = NUMBERFORMAT_OP_GT;
1697 break;
1698 case '=':
1699 eSymbolType = NUMBERFORMAT_OP_EQ;
1700 break;
1702 break;
1703 case ' ':
1704 nPos--;
1705 rString.remove(nPos, 1);
1706 nLen--;
1707 break;
1708 case '$' :
1709 if ( nPos < nLen && rString[nPos] == '-' )
1711 // [$-xxx] locale
1712 sBuffSymbol.stripStart('[');
1713 eSymbolType = BRACKET_SYMBOLTYPE_LOCALE;
1714 eState = SsGetPrefix;
1716 else
1717 { // currency
1718 eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
1719 eState = SsGetString;
1721 sBuffSymbol.append(cToken);
1722 break;
1723 case '~' :
1724 // calendarID
1725 eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
1726 sBuffSymbol.append(cToken);
1727 eState = SsGetString;
1728 break;
1729 default:
1731 static const OUStringLiteral aNatNum(u"NATNUM");
1732 static const OUStringLiteral aDBNum(u"DBNUM");
1733 const OUString aBufStr( rString.toString());
1734 sal_Int32 nNatNumNum;
1735 sal_Int32 nDBNum;
1736 if ( lcl_matchKeywordAndGetNumber( aBufStr, nPos-1, aNatNum, nNatNumNum) &&
1737 0 <= nNatNumNum && nNatNumNum <= 19 )
1739 sBuffSymbol.stripStart('[');
1740 sBuffSymbol.append( aBufStr.subView(--nPos, aNatNum.getLength()+1) );
1741 nPos += aNatNum.getLength()+1;
1742 //! SymbolType is negative
1743 eSymbolType = static_cast<short>(BRACKET_SYMBOLTYPE_NATNUM0 - nNatNumNum);
1744 eState = SsGetPrefix;
1746 else if ( lcl_matchKeywordAndGetNumber( aBufStr, nPos-1, aDBNum, nDBNum) &&
1747 1 <= nDBNum && nDBNum <= 9 )
1749 sBuffSymbol.stripStart('[');
1750 sBuffSymbol.append( aBufStr.subView(--nPos, aDBNum.getLength()+1) );
1751 nPos += aDBNum.getLength()+1;
1752 //! SymbolType is negative
1753 eSymbolType = sal::static_int_cast< short >( BRACKET_SYMBOLTYPE_DBNUM1 - (nDBNum - 1) );
1754 eState = SsGetPrefix;
1756 else
1758 sal_Unicode cUpper = rChrCls().uppercase( aBufStr, nPos-1, 1)[0];
1759 if ( cUpper == rKeywords[NF_KEY_H][0] || // H
1760 cUpper == rKeywords[NF_KEY_MI][0] || // M
1761 cUpper == rKeywords[NF_KEY_S][0] ) // S
1763 sBuffSymbol.append(cToken);
1764 eState = SsGetTime;
1765 cLetter = cToken;
1767 else
1769 sBuffSymbol.stripStart('[');
1770 sBuffSymbol.append(cToken);
1771 eSymbolType = BRACKET_SYMBOLTYPE_COLOR;
1772 eState = SsGetPrefix;
1777 break;
1778 case SsGetString:
1779 if (cToken == '\"')
1781 eState = SsGetQuoted;
1782 sBuffSymbol.append(cToken);
1784 else if (cToken == ';' && (nPos < 2 || !IsCombiningSymbol( rString, nPos-2)))
1786 eState = SsStop;
1788 else
1790 sBuffSymbol.append(cToken);
1792 break;
1793 case SsGetQuoted:
1794 if (cToken == '\"')
1796 eState = SsGetString;
1797 sBuffSymbol.append(cToken);
1799 else
1801 sBuffSymbol.append(cToken);
1803 break;
1804 case SsGetTime:
1805 if (cToken == ']')
1807 sBuffSymbol.append(cToken);
1808 eState = SsGetString;
1809 eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
1811 else
1813 sal_Unicode cUpper = rChrCls().uppercase(rString.toString(), nPos-1, 1)[0];
1814 if (cUpper == rKeywords[NF_KEY_H][0] || // H
1815 cUpper == rKeywords[NF_KEY_MI][0] || // M
1816 cUpper == rKeywords[NF_KEY_S][0] ) // S
1818 if (cLetter == cToken)
1820 sBuffSymbol.append(cToken);
1821 cLetter = ' ';
1823 else
1825 sBuffSymbol.stripStart('[');
1826 sBuffSymbol.append(cToken);
1827 eState = SsGetPrefix;
1830 else
1832 sBuffSymbol.stripStart('[');
1833 sBuffSymbol.append(cToken);
1834 eSymbolType = BRACKET_SYMBOLTYPE_COLOR;
1835 eState = SsGetPrefix;
1838 break;
1839 case SsGetCon:
1840 switch (cToken)
1842 case '<':
1843 eState = SsStop;
1844 eSymbolType = BRACKET_SYMBOLTYPE_ERROR;
1845 break;
1846 case '>':
1847 if (cLetter == '<')
1849 sBuffSymbol.append(cToken);
1850 cLetter = ' ';
1851 eState = SsStop;
1852 eSymbolType = NUMBERFORMAT_OP_NE;
1854 else
1856 eState = SsStop;
1857 eSymbolType = BRACKET_SYMBOLTYPE_ERROR;
1859 break;
1860 case '=':
1861 if (cLetter == '<')
1863 sBuffSymbol.append(cToken);
1864 cLetter = ' ';
1865 eSymbolType = NUMBERFORMAT_OP_LE;
1867 else if (cLetter == '>')
1869 sBuffSymbol.append(cToken);
1870 cLetter = ' ';
1871 eSymbolType = NUMBERFORMAT_OP_GE;
1873 else
1875 eState = SsStop;
1876 eSymbolType = BRACKET_SYMBOLTYPE_ERROR;
1878 break;
1879 case ' ':
1880 nPos--;
1881 rString.remove(nPos,1);
1882 nLen--;
1883 break;
1884 default:
1885 eState = SsStop;
1886 nPos--;
1887 break;
1889 break;
1890 case SsGetPrefix:
1891 if (cToken == ']')
1893 eState = SsStop;
1895 else
1897 sBuffSymbol.append(cToken);
1899 break;
1900 default:
1901 break;
1902 } // of switch
1903 } // of while
1904 sSymbol = sBuffSymbol.makeStringAndClear();
1905 return eSymbolType;
1908 void SvNumberformat::ConvertLanguage( SvNumberFormatter& rConverter,
1909 LanguageType eConvertFrom,
1910 LanguageType eConvertTo )
1912 sal_Int32 nCheckPos;
1913 sal_uInt32 nKey;
1914 SvNumFormatType nType = eType;
1915 OUString aFormatString( sFormatstring );
1916 rConverter.PutandConvertEntry( aFormatString, nCheckPos, nType,
1917 nKey, eConvertFrom, eConvertTo, false);
1918 const SvNumberformat* pFormat = rConverter.GetEntry( nKey );
1919 DBG_ASSERT( pFormat, "SvNumberformat::ConvertLanguage: Conversion without format" );
1920 if ( pFormat )
1922 ImpCopyNumberformat( *pFormat );
1923 // Reset values taken over from Formatter/Scanner
1924 // pColor still points to table in temporary Formatter/Scanner
1925 for (ImpSvNumFor & rFormatter : NumFor)
1927 OUString aColorName = rFormatter.GetColorName();
1928 const Color* pColor = rScan.GetColor( aColorName );
1929 rFormatter.SetColor( pColor, aColorName );
1934 bool SvNumberformat::HasNewCurrency() const
1936 for (const auto & j : NumFor)
1938 if ( j.HasNewCurrency() )
1940 return true;
1943 return false;
1946 bool SvNumberformat::GetNewCurrencySymbol( OUString& rSymbol,
1947 OUString& rExtension ) const
1949 for (const auto & j : NumFor)
1951 if ( j.GetNewCurrencySymbol( rSymbol, rExtension ) )
1953 return true;
1956 rSymbol.clear();
1957 rExtension.clear();
1958 return false;
1961 // static
1962 OUString SvNumberformat::StripNewCurrencyDelimiters( const OUString& rStr )
1964 OUStringBuffer aTmp(rStr.getLength());
1965 sal_Int32 nStartPos, nPos, nLen;
1966 nLen = rStr.getLength();
1967 nStartPos = 0;
1968 while ( (nPos = rStr.indexOf( "[$", nStartPos )) >= 0 )
1970 sal_Int32 nEnd;
1971 if ( (nEnd = GetQuoteEnd( rStr, nPos )) >= 0 )
1973 aTmp.append(rStr.subView( nStartPos, ++nEnd - nStartPos ));
1974 nStartPos = nEnd;
1976 else
1978 aTmp.append(rStr.subView(nStartPos, nPos - nStartPos) );
1979 nStartPos = nPos + 2;
1980 sal_Int32 nDash;
1981 nEnd = nStartPos - 1;
1984 nDash = rStr.indexOf( '-', ++nEnd );
1985 nEnd = GetQuoteEnd( rStr, nDash );
1987 while ( nEnd >= 0 );
1988 sal_Int32 nClose;
1989 nEnd = nStartPos - 1;
1992 nClose = rStr.indexOf( ']', ++nEnd );
1993 nEnd = GetQuoteEnd( rStr, nClose );
1995 while ( nEnd >= 0 );
1997 if(nClose < 0)
1999 /* there should always be a closing ]
2000 * but the old String class would have hidden
2001 * that. so be conservative too
2003 nClose = nLen;
2006 nPos = nClose;
2007 if(nDash >= 0 && nDash < nClose)
2009 nPos = nDash;
2011 aTmp.append(rStr.subView(nStartPos, nPos - nStartPos) );
2012 nStartPos = nClose + 1;
2015 if ( nLen > nStartPos )
2017 aTmp.append(rStr.subView(nStartPos, nLen - nStartPos) );
2019 return aTmp.makeStringAndClear();
2022 void SvNumberformat::ImpGetOutputStandard(double& fNumber, OUStringBuffer& rOutString) const
2024 OUString sTemp;
2025 ImpGetOutputStandard(fNumber, sTemp);
2026 rOutString = sTemp;
2029 void SvNumberformat::ImpGetOutputStandard(double& fNumber, OUString& rOutString) const
2031 sal_uInt16 nStandardPrec = rScan.GetStandardPrec();
2033 if ( fabs(fNumber) > EXP_ABS_UPPER_BOUND )
2035 nStandardPrec = ::std::min(nStandardPrec, static_cast<sal_uInt16>(14)); // limits to 14 decimals
2036 rOutString = ::rtl::math::doubleToUString( fNumber,
2037 rtl_math_StringFormat_E2, nStandardPrec /*2*/,
2038 GetFormatter().GetNumDecimalSep()[0]);
2040 else
2042 ImpGetOutputStdToPrecision(fNumber, rOutString, nStandardPrec);
2046 namespace
2049 template<typename T>
2050 bool checkForAll0s(const T& rString, sal_Int32 nIdx=0)
2052 if (nIdx>=rString.getLength())
2053 return false;
2057 if (rString[nIdx]!='0')
2058 return false;
2060 while (++nIdx<rString.getLength());
2062 return true;
2067 void SvNumberformat::ImpGetOutputStdToPrecision(double& rNumber, OUString& rOutString, sal_uInt16 nPrecision) const
2069 // Make sure the precision doesn't go over the maximum allowable precision.
2070 nPrecision = ::std::min(UPPER_PRECISION, nPrecision);
2072 // We decided to strip trailing zeros unconditionally, since binary
2073 // double-precision rounding error makes it impossible to determine e.g.
2074 // whether 844.10000000000002273737 is what the user has typed, or the
2075 // user has typed 844.1 but IEEE 754 represents it that way internally.
2077 rOutString = ::rtl::math::doubleToUString( rNumber,
2078 rtl_math_StringFormat_F, nPrecision /*2*/,
2079 GetFormatter().GetNumDecimalSep()[0], true );
2080 if (rOutString[0] == '-' && checkForAll0s(rOutString, 1))
2082 rOutString = comphelper::string::stripStart(rOutString, '-'); // not -0
2084 rOutString = impTransliterate(rOutString, NumFor[0].GetNatNum());
2087 void SvNumberformat::ImpGetOutputInputLine(double fNumber, OUString& OutString) const
2089 bool bModified = false;
2090 if ( (eType & SvNumFormatType::PERCENT) && (fabs(fNumber) < D_MAX_D_BY_100))
2092 if (fNumber == 0.0)
2094 OutString = "0%";
2095 return;
2097 fNumber *= 100;
2098 bModified = true;
2101 if (fNumber == 0.0)
2103 OutString = "0";
2104 return;
2107 OutString = ::rtl::math::doubleToUString( fNumber,
2108 rtl_math_StringFormat_Automatic,
2109 rtl_math_DecimalPlaces_Max,
2110 GetFormatter().GetNumDecimalSep()[0], true );
2112 if ( eType & SvNumFormatType::PERCENT && bModified)
2114 OutString += "%";
2118 short SvNumberformat::ImpCheckCondition(double fNumber,
2119 double fLimit,
2120 SvNumberformatLimitOps eOp)
2122 switch(eOp)
2124 case NUMBERFORMAT_OP_NO:
2125 return -1;
2126 case NUMBERFORMAT_OP_EQ:
2127 return static_cast<short>(fNumber == fLimit);
2128 case NUMBERFORMAT_OP_NE:
2129 return static_cast<short>(fNumber != fLimit);
2130 case NUMBERFORMAT_OP_LT:
2131 return static_cast<short>(fNumber < fLimit);
2132 case NUMBERFORMAT_OP_LE:
2133 return static_cast<short>(fNumber <= fLimit);
2134 case NUMBERFORMAT_OP_GT:
2135 return static_cast<short>(fNumber > fLimit);
2136 case NUMBERFORMAT_OP_GE:
2137 return static_cast<short>(fNumber >= fLimit);
2138 default:
2139 return -1;
2143 static bool lcl_appendStarFillChar( OUStringBuffer& rBuf, const OUString& rStr )
2145 // Right during user input the star symbol is the very
2146 // last character before the user enters another one.
2147 if (rStr.getLength() > 1)
2149 rBuf.append(u'\x001B');
2150 rBuf.append(rStr[1]);
2151 return true;
2153 return false;
2156 static bool lcl_insertStarFillChar( OUStringBuffer& rBuf, sal_Int32 nPos, const OUString& rStr )
2158 if (rStr.getLength() > 1)
2160 rBuf.insert( nPos, rStr[1]);
2161 rBuf.insert( nPos, u'\x001B');
2162 return true;
2164 return false;
2167 void SvNumberformat::GetOutputString(std::u16string_view sString,
2168 OUString& OutString,
2169 const Color** ppColor)
2171 OUStringBuffer sOutBuff;
2172 sal_uInt16 nIx;
2173 if (eType & SvNumFormatType::TEXT)
2175 nIx = 0;
2177 else if (NumFor[3].GetCount() > 0)
2179 nIx = 3;
2181 else
2183 *ppColor = nullptr; // no change of color
2184 return;
2186 *ppColor = NumFor[nIx].GetColor();
2187 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
2188 if (rInfo.eScannedType == SvNumFormatType::TEXT)
2190 const sal_uInt16 nCnt = NumFor[nIx].GetCount();
2191 for (sal_uInt16 i = 0; i < nCnt; i++)
2193 switch (rInfo.nTypeArray[i])
2195 case NF_SYMBOLTYPE_STAR:
2196 if( bStarFlag )
2198 lcl_appendStarFillChar( sOutBuff, rInfo.sStrArray[i]);
2200 break;
2201 case NF_SYMBOLTYPE_BLANK:
2202 if (rInfo.sStrArray[i].getLength() >= 2)
2203 InsertBlanks( sOutBuff, sOutBuff.getLength(), rInfo.sStrArray[i][1] );
2204 break;
2205 case NF_KEY_GENERAL : // #77026# "General" is the same as "@"
2206 case NF_SYMBOLTYPE_DEL :
2207 sOutBuff.append(sString);
2208 break;
2209 default:
2210 sOutBuff.append(rInfo.sStrArray[i]);
2214 OutString = sOutBuff.makeStringAndClear();
2217 namespace {
2219 void lcl_GetOutputStringScientific(double fNumber, sal_uInt16 nCharCount,
2220 const SvNumberFormatter& rFormatter, OUString& rOutString)
2222 bool bSign = std::signbit(fNumber);
2224 // 1.000E+015 (one digit and the decimal point, and the two chars +
2225 // nExpDigit for the exponential part, totalling 6 or 7).
2226 double fExp = log10( fabs(fNumber) );
2227 if( fExp < 0.0 )
2228 fExp = 1.0 - fExp;
2229 sal_uInt16 nCharFormat = 6 + (fExp >= 100.0 ? 1 : 0);
2230 sal_uInt16 nPrec = nCharCount > nCharFormat ? nCharCount - nCharFormat : 0;
2231 if (nPrec && bSign)
2233 // Make room for the negative sign.
2234 --nPrec;
2236 nPrec = ::std::min(nPrec, static_cast<sal_uInt16>(14)); // limit to 14 decimals.
2238 rOutString = ::rtl::math::doubleToUString(fNumber, rtl_math_StringFormat_E2,
2239 nPrec, rFormatter.GetNumDecimalSep()[0], true );
2242 OUString lcl_GetPercentString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt)
2244 sal_Int32 i;
2245 OUStringBuffer aPercentString;
2246 for( i = 0; i < nCnt; i++ )
2248 if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_PERCENT )
2250 aPercentString.append( rInfo.sStrArray[i] );
2251 bool bStringFound = false;
2252 for( i--; i >= 0 && rInfo.nTypeArray[i] == NF_SYMBOLTYPE_STRING ; i-- )
2254 if( !bStringFound )
2256 bStringFound = true;
2257 aPercentString.insert( 0, "\"" );
2259 aPercentString.insert( 0, rInfo.sStrArray[i] );
2261 i = nCnt;
2262 if( bStringFound )
2263 aPercentString.insert( 0, "\"" );
2266 return aPercentString.makeStringAndClear();
2269 OUString lcl_GetDenominatorString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt)
2271 sal_Int32 i;
2272 OUStringBuffer aDenominatorString;
2273 for( i = 0; i < nCnt; i++ )
2275 if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC )
2277 while ( ( ++i < nCnt ) && rInfo.nTypeArray[i] != NF_SYMBOLTYPE_FRAC_FDIV
2278 && rInfo.nTypeArray[i] != NF_SYMBOLTYPE_DIGIT );
2279 for( ; i < nCnt; i++ )
2281 if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC_FDIV || rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT )
2282 aDenominatorString.append( rInfo.sStrArray[i] );
2283 else
2284 i = nCnt;
2288 return aDenominatorString.makeStringAndClear();
2291 OUString lcl_GetNumeratorString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt)
2293 sal_Int32 i;
2294 OUStringBuffer aNumeratorString;
2295 for( i = 0; i < nCnt; i++ )
2297 if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC )
2299 for( i--; i >= 0 && rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT ; i-- )
2301 aNumeratorString.insert( 0, rInfo.sStrArray[i] );
2303 i = nCnt;
2306 return aNumeratorString.makeStringAndClear();
2309 OUString lcl_GetFractionIntegerString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt)
2311 sal_Int32 i;
2312 OUStringBuffer aIntegerString;
2313 for( i = 0; i < nCnt; i++ )
2315 if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRACBLANK )
2317 for( i--; i >= 0 && ( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT
2318 || rInfo.nTypeArray[i] == NF_SYMBOLTYPE_THSEP ); i-- )
2320 aIntegerString.insert( 0, rInfo.sStrArray[i] );
2322 i = nCnt;
2325 return aIntegerString.makeStringAndClear();
2328 OUString lcl_GetIntegerFractionDelimiterString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt)
2330 sal_uInt16 i;
2331 for( i = 0; i < nCnt; i++ )
2333 if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRACBLANK )
2335 return rInfo.sStrArray[i];
2338 return OUString();
2343 OUString SvNumberformat::GetPercentString( sal_uInt16 nNumFor ) const
2345 const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info();
2346 sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
2347 return lcl_GetPercentString( rInfo, nCnt );
2350 OUString SvNumberformat::GetDenominatorString( sal_uInt16 nNumFor ) const
2352 const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info();
2353 sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
2354 return lcl_GetDenominatorString( rInfo, nCnt );
2357 OUString SvNumberformat::GetNumeratorString( sal_uInt16 nNumFor ) const
2359 const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info();
2360 sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
2361 return lcl_GetNumeratorString( rInfo, nCnt );
2364 OUString SvNumberformat::GetIntegerFractionDelimiterString( sal_uInt16 nNumFor ) const
2366 const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info();
2367 sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
2368 return lcl_GetIntegerFractionDelimiterString( rInfo, nCnt );
2371 bool SvNumberformat::GetOutputString(double fNumber, sal_uInt16 nCharCount, OUString& rOutString) const
2373 if (eType != SvNumFormatType::NUMBER)
2375 return false;
2377 double fTestNum = fNumber;
2378 bool bSign = std::signbit(fTestNum);
2379 if (bSign)
2381 fTestNum = -fTestNum;
2383 if (fTestNum < EXP_LOWER_BOUND)
2385 lcl_GetOutputStringScientific(fNumber, nCharCount, GetFormatter(), rOutString);
2386 return true;
2389 double fExp = log10(fTestNum);
2390 // Values < 1.0 always have one digit before the decimal point.
2391 sal_uInt16 nDigitPre = fExp >= 0.0 ? static_cast<sal_uInt16>(ceil(fExp)) : 1;
2393 if (nDigitPre > 15)
2395 lcl_GetOutputStringScientific(fNumber, nCharCount, GetFormatter(), rOutString);
2396 return true;
2399 sal_uInt16 nPrec = nCharCount >= nDigitPre ? nCharCount - nDigitPre : 0;
2400 if (nPrec && bSign)
2402 // Subtract the negative sign.
2403 --nPrec;
2405 if (nPrec)
2407 // Subtract the decimal point.
2408 --nPrec;
2410 ImpGetOutputStdToPrecision(fNumber, rOutString, nPrec);
2411 if (rOutString.getLength() > nCharCount)
2413 // String still wider than desired. Switch to scientific notation.
2414 lcl_GetOutputStringScientific(fNumber, nCharCount, GetFormatter(), rOutString);
2416 return true;
2419 sal_uInt16 SvNumberformat::GetSubformatIndex (double fNumber ) const
2421 sal_uInt16 nIx; // Index of the partial format
2422 double fLimit_1 = fLimit1;
2423 short nCheck = ImpCheckCondition(fNumber, fLimit_1, eOp1);
2424 if (nCheck == -1 || nCheck == 1) // Only 1 String or True
2426 nIx = 0;
2428 else
2430 double fLimit_2 = fLimit2;
2431 nCheck = ImpCheckCondition(fNumber, fLimit_2, eOp2);
2432 if (nCheck == -1 || nCheck == 1)
2434 nIx = 1;
2436 else
2438 nIx = 2;
2441 return nIx;
2444 bool SvNumberformat::GetOutputString(double fNumber,
2445 OUString& OutString,
2446 const Color** ppColor)
2448 bool bRes = false;
2449 OutString.clear();
2450 *ppColor = nullptr; // No color change
2451 if (eType & SvNumFormatType::LOGICAL && sFormatstring == rScan.GetKeywords()[NF_KEY_BOOLEAN])
2453 if (fNumber)
2455 OutString = rScan.GetTrueString();
2457 else
2459 OutString = rScan.GetFalseString();
2461 return false;
2463 OUStringBuffer sBuff(64);
2464 if (eType & SvNumFormatType::TEXT)
2466 ImpGetOutputStandard(fNumber, sBuff);
2467 OutString = sBuff.makeStringAndClear();
2468 return false;
2470 bool bHadStandard = false;
2471 if (bStandard) // Individual standard formats
2473 if (rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION) // All number format InputLine
2475 ImpGetOutputInputLine(fNumber, OutString);
2476 return false;
2478 switch (eType)
2480 case SvNumFormatType::NUMBER: // Standard number format
2481 if (rScan.GetStandardPrec() == SvNumberFormatter::UNLIMITED_PRECISION)
2483 if (std::signbit(fNumber))
2485 if (!(fNumber < 0.0))
2486 fNumber = -fNumber; // do not display -0.0
2488 if (fNumber == 0.0)
2490 OutString = "0";
2492 else if (fNumber < 1.0 && fNumber > -1.0)
2494 // Decide whether to display as 0.000000123... or 1.23...e-07
2495 bool bFix = (fNumber < -EXP_LOWER_BOUND || EXP_LOWER_BOUND < fNumber);
2496 if (!bFix)
2498 // Arbitrary, not too many 0s visually, start E2 at 1E-10.
2499 constexpr sal_Int32 kMaxExp = 9;
2500 const sal_Int32 nExp = static_cast<sal_Int32>(ceil( -log10( fabs( fNumber))));
2501 if (nExp <= kMaxExp && rtl::math::approxEqual(
2502 rtl::math::round( fNumber, 16), rtl::math::round( fNumber, nExp + 16)))
2504 // Not too many significant digits or accuracy
2505 // artefacts, otherwise leave everything to E2
2506 // format.
2507 bFix = true;
2510 if (bFix)
2511 OutString = ::rtl::math::doubleToUString( fNumber,
2512 rtl_math_StringFormat_F,
2513 rtl_math_DecimalPlaces_Max,
2514 GetFormatter().GetNumDecimalSep()[0], true);
2515 else
2516 OutString = ::rtl::math::doubleToUString( fNumber,
2517 rtl_math_StringFormat_E2,
2518 rtl_math_DecimalPlaces_Max,
2519 GetFormatter().GetNumDecimalSep()[0], true);
2521 else
2523 OutString = ::rtl::math::doubleToUString( fNumber,
2524 rtl_math_StringFormat_Automatic,
2525 rtl_math_DecimalPlaces_Max,
2526 GetFormatter().GetNumDecimalSep()[0], true);
2528 return false;
2530 ImpGetOutputStandard(fNumber, sBuff);
2531 bHadStandard = true;
2532 break;
2533 case SvNumFormatType::DATE:
2534 bRes |= ImpGetDateOutput(fNumber, 0, sBuff);
2535 bHadStandard = true;
2536 break;
2537 case SvNumFormatType::TIME:
2538 bRes |= ImpGetTimeOutput(fNumber, 0, sBuff);
2539 bHadStandard = true;
2540 break;
2541 case SvNumFormatType::DATETIME:
2542 bRes |= ImpGetDateTimeOutput(fNumber, 0, sBuff);
2543 bHadStandard = true;
2544 break;
2545 default: break;
2548 if ( !bHadStandard )
2550 sal_uInt16 nIx = GetSubformatIndex ( fNumber ); // Index of the partial format
2551 if (fNumber < 0.0 &&
2552 ((nIx == 0 && IsFirstSubformatRealNegative()) || // 1st, usually positive subformat
2553 (nIx == 1 && IsSecondSubformatRealNegative()))) // 2nd, usually negative subformat
2555 fNumber = -fNumber; // eliminate sign
2557 *ppColor = NumFor[nIx].GetColor();
2558 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
2559 const sal_uInt16 nCnt = NumFor[nIx].GetCount();
2560 if (nCnt == 0 && rInfo.eScannedType == SvNumFormatType::EMPTY)
2562 return false; // Empty => nothing
2564 else if (nCnt == 0) // Else Standard Format
2566 ImpGetOutputStandard(fNumber, sBuff);
2567 OutString = sBuff.makeStringAndClear();
2568 return false;
2570 switch (rInfo.eScannedType)
2572 case SvNumFormatType::TEXT:
2573 case SvNumFormatType::DEFINED:
2574 for (sal_uInt16 i = 0; i < nCnt; i++)
2576 switch (rInfo.nTypeArray[i])
2578 case NF_SYMBOLTYPE_STAR:
2579 if( bStarFlag )
2581 bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]);
2583 break;
2584 case NF_SYMBOLTYPE_BLANK:
2585 if (rInfo.sStrArray[i].getLength() >= 2)
2586 InsertBlanks(sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] );
2587 break;
2588 case NF_SYMBOLTYPE_STRING:
2589 case NF_SYMBOLTYPE_CURRENCY:
2590 sBuff.append(rInfo.sStrArray[i]);
2591 break;
2592 case NF_SYMBOLTYPE_THSEP:
2593 if (rInfo.nThousand == 0)
2595 sBuff.append(rInfo.sStrArray[i]);
2597 break;
2598 default:
2599 break;
2602 break;
2603 case SvNumFormatType::DATE:
2604 bRes |= ImpGetDateOutput(fNumber, nIx, sBuff);
2605 break;
2606 case SvNumFormatType::TIME:
2607 bRes |= ImpGetTimeOutput(fNumber, nIx, sBuff);
2608 break;
2609 case SvNumFormatType::DATETIME:
2610 bRes |= ImpGetDateTimeOutput(fNumber, nIx, sBuff);
2611 break;
2612 case SvNumFormatType::NUMBER:
2613 case SvNumFormatType::PERCENT:
2614 case SvNumFormatType::CURRENCY:
2615 bRes |= ImpGetNumberOutput(fNumber, nIx, sBuff);
2616 break;
2617 case SvNumFormatType::LOGICAL:
2618 bRes |= ImpGetLogicalOutput(fNumber, nIx, sBuff);
2619 break;
2620 case SvNumFormatType::FRACTION:
2621 bRes |= ImpGetFractionOutput(fNumber, nIx, sBuff);
2622 break;
2623 case SvNumFormatType::SCIENTIFIC:
2624 bRes |= ImpGetScientificOutput(fNumber, nIx, sBuff);
2625 break;
2626 default: break;
2629 OutString = sBuff.makeStringAndClear();
2630 return bRes;
2633 bool SvNumberformat::ImpGetScientificOutput(double fNumber,
2634 sal_uInt16 nIx,
2635 OUStringBuffer& sStr)
2637 bool bRes = false;
2638 bool bSign = false;
2640 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
2641 const sal_uInt16 nCnt = NumFor[nIx].GetCount();
2643 if (fNumber < 0)
2645 if (nIx == 0) // Not in the ones at the end
2647 bSign = true; // Formats
2649 fNumber = -fNumber;
2652 sStr = ::rtl::math::doubleToUString( fNumber,
2653 rtl_math_StringFormat_E,
2654 rInfo.nCntPre + rInfo.nCntPost - 1, '.' );
2655 OUStringBuffer ExpStr;
2656 short nExpSign = 1;
2657 sal_Int32 nExPos = sStr.indexOf('E');
2658 sal_Int32 nDecPos = -1;
2660 if ( nExPos >= 0 )
2662 // split into mantissa and exponent and get rid of "E+" or "E-"
2663 sal_Int32 nExpStart = nExPos + 1;
2665 switch ( sStr[ nExpStart ] )
2667 case '-' :
2668 nExpSign = -1;
2669 [[fallthrough]];
2670 case '+' :
2671 ++nExpStart;
2672 break;
2674 ExpStr = sStr.subView( nExpStart ); // part following the "E+"
2675 sStr.truncate( nExPos );
2677 if ( rInfo.nCntPre != 1 ) // rescale Exp
2679 sal_Int32 nExp = ExpStr.toString().toInt32() * nExpSign;
2680 sal_Int32 nRescale = (rInfo.nCntPre != 0) ? nExp % static_cast<sal_Int32>(rInfo.nCntPre) : -1;
2681 if( nRescale < 0 && rInfo.nCntPre != 0 )
2682 nRescale += static_cast<sal_Int32>(rInfo.nCntPre);
2683 nExp -= nRescale;
2684 if ( nExp < 0 )
2686 nExpSign = -1;
2687 nExp = -nExp;
2689 else
2691 nExpSign = 1;
2693 ExpStr = OUString::number( nExp );
2694 const sal_Unicode cFirstDigit = sStr[0];
2695 // rescale mantissa
2696 sStr = ::rtl::math::doubleToUString( fNumber,
2697 rtl_math_StringFormat_E,
2698 nRescale + rInfo.nCntPost, '.' );
2700 // sStr now may contain a rounded-up value shifted into the next
2701 // magnitude, for example 1.000E+02 (4 digits) for fNumber 99.995
2702 // (9.9995E+02 rounded to 3 decimals) but we want the final result
2703 // to be 100.00E+00 (5 digits), so for the following fill routines
2704 // below to work correctly append a zero decimal.
2705 /* TODO: this is awkward, could an engineering notation mode be
2706 * introduced to rtl_math_doubleToUString()? */
2707 sStr.truncate( sStr.indexOf('E') );
2708 if (sStr[0] == '1' && cFirstDigit != '1')
2709 sStr.append('0');
2712 // cut any decimal delimiter
2713 sal_Int32 index = 0;
2715 while((index = sStr.indexOf('.', index)) >= 0)
2717 if (nDecPos < 0)
2718 nDecPos = index;
2719 sStr.remove(index, 1);
2723 sal_uInt16 j = nCnt-1; // Last symbol
2724 sal_Int32 k; // Position in ExpStr
2725 sal_Int32 nZeros = 0; // Erase leading zeros
2727 bRes |= ImpNumberFill(ExpStr, fNumber, k, j, nIx, NF_SYMBOLTYPE_EXP);
2729 while (nZeros < k && ExpStr[nZeros] == '0')
2731 ++nZeros;
2733 if (nZeros)
2735 ExpStr.remove( 0, nZeros);
2738 bool bCont = true;
2740 if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_EXP)
2742 const OUString& rStr = rInfo.sStrArray[j];
2743 if (nExpSign == -1)
2745 ExpStr.insert(0, '-');
2747 else if (rStr.getLength() > 1 && rStr[1] == '+')
2749 ExpStr.insert(0, '+');
2751 ExpStr.insert(0, rStr[0]);
2752 if ( j )
2754 j--;
2756 else
2758 bCont = false;
2761 // Continue main number:
2762 if ( !bCont )
2764 sStr.truncate();
2766 else
2768 bRes |= ImpDecimalFill(sStr, fNumber, nDecPos, j, nIx, false);
2771 if (bSign)
2773 sStr.insert(0, '-');
2775 sStr.append(ExpStr);
2777 return bRes;
2780 double SvNumberformat::GetRoundFractionValue ( double fNumber ) const
2782 sal_uInt16 nIx = GetSubformatIndex ( fNumber );
2783 double fIntPart = 0.0; // integer part of fraction
2784 sal_Int64 nFrac = 0, nDiv = 1; // numerator and denominator
2785 double fSign = (fNumber < 0.0) ? -1.0 : 1.0;
2786 // fNumber is modified in ImpGetFractionElements to absolute fractional part
2787 ImpGetFractionElements ( fNumber, nIx, fIntPart, nFrac, nDiv );
2788 if ( nDiv > 0 )
2789 return fSign * ( fIntPart + static_cast<double>(nFrac) / static_cast<double>(nDiv) );
2790 else
2791 return fSign * fIntPart;
2794 void SvNumberformat::ImpGetFractionElements ( double& fNumber, sal_uInt16 nIx,
2795 double& fIntPart, sal_Int64& nFrac, sal_Int64& nDiv ) const
2797 if ( fNumber < 0.0 )
2798 fNumber = -fNumber;
2799 fIntPart = floor(fNumber); // Integral part
2800 fNumber -= fIntPart; // Fractional part
2801 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
2802 nDiv = lcl_GetDenominatorString( rInfo, NumFor[nIx].GetCount() ).toInt32();
2803 if( nDiv > 0 )
2804 { // Forced Denominator
2805 nFrac = static_cast<sal_Int64>(floor ( fNumber * nDiv ));
2806 double fFracNew = static_cast<double>(nFrac) / static_cast<double>(nDiv);
2807 double fFracNew1 = static_cast<double>(nFrac + 1) / static_cast<double>(nDiv);
2808 double fDiff = fNumber - fFracNew;
2809 if( fDiff > ( fFracNew1 - fNumber ) )
2811 nFrac++;
2814 else // Calculated Denominator
2816 nDiv = 1;
2817 sal_Int64 nBasis = static_cast<sal_Int64>(floor( pow(10.0,rInfo.nCntExp))) - 1; // 9, 99, 999 ,...
2818 sal_Int64 nFracPrev = 1, nDivPrev = 0, nFracNext, nDivNext, nPartialDenom;
2819 double fRemainder = fNumber;
2821 // Use continued fraction representation of fNumber
2822 // See https://en.wikipedia.org/wiki/Continued_fraction#Best_rational_approximations
2823 while ( fRemainder > 0.0 )
2825 double fTemp = 1.0 / fRemainder; // 64bits precision required when fRemainder is very weak
2826 nPartialDenom = static_cast<sal_Int64>(floor(fTemp)); // due to floating point notation with double precision
2827 fRemainder = fTemp - static_cast<double>(nPartialDenom);
2828 nDivNext = nPartialDenom * nDiv + nDivPrev;
2829 if ( nDivNext <= nBasis ) // continue loop
2831 nFracNext = nPartialDenom * nFrac + nFracPrev;
2832 nFracPrev = nFrac;
2833 nFrac = nFracNext;
2834 nDivPrev = nDiv;
2835 nDiv = nDivNext;
2837 else // calculate collateral fraction and exit
2839 sal_Int64 nCollat = (nBasis - nDivPrev) / nDiv;
2840 if ( 2 * nCollat >= nPartialDenom )
2842 sal_Int64 nFracTest = nCollat * nFrac + nFracPrev;
2843 sal_Int64 nDivTest = nCollat * nDiv + nDivPrev;
2844 double fSign = (static_cast<double>(nFrac) > fNumber * static_cast<double>(nDiv))?1.0:-1.0;
2845 if ( fSign * ( double(nFrac * nDivTest + nDiv * nFracTest) - 2.0 * double(nDiv * nDivTest) * fNumber ) > 0.0 )
2847 nFrac = nFracTest;
2848 nDiv = nDivTest;
2851 fRemainder = 0.0; // exit while loop
2855 if (nFrac >= nDiv)
2857 ++fIntPart;
2858 nFrac = nDiv = 0;
2862 bool SvNumberformat::ImpGetFractionOutput(double fNumber,
2863 sal_uInt16 nIx,
2864 OUStringBuffer& sBuff)
2866 bool bRes = false;
2867 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
2868 const sal_uInt16 nCnt = NumFor[nIx].GetCount();
2869 OUStringBuffer sStr, sFrac, sDiv; // Strings, value for Integral part Numerator and denominator
2870 bool bSign = ( (fNumber < 0) && (nIx == 0) ); // sign Not in the ones at the end
2871 const OUString sIntegerFormat = lcl_GetFractionIntegerString(rInfo, nCnt);
2872 const OUString sNumeratorFormat = lcl_GetNumeratorString(rInfo, nCnt);
2873 const OUString sDenominatorFormat = lcl_GetDenominatorString(rInfo, nCnt);
2875 sal_Int64 nFrac = 0, nDiv = 1;
2876 double fNum = floor(fNumber); // Integral part
2878 if (fNum > D_MAX_U_INT32 || rInfo.nCntExp > 9) // Too large
2880 sBuff = ImpSvNumberformatScan::sErrStr;
2881 return false;
2883 if (rInfo.nCntExp == 0)
2885 SAL_WARN( "svl.numbers", "SvNumberformat:: Fraction, nCntExp == 0");
2886 sBuff.truncate();
2887 return false;
2890 ImpGetFractionElements( fNumber, nIx, fNum, nFrac, nDiv);
2892 if (rInfo.nCntPre == 0) // Improper fraction
2894 double fNum1 = fNum * static_cast<double>(nDiv) + static_cast<double>(nFrac);
2896 if (fNum1 > D_MAX_INTEGER)
2898 sBuff = ImpSvNumberformatScan::sErrStr;
2899 return false;
2901 nFrac = static_cast<sal_Int64>(floor(fNum1));
2903 else if (fNum == 0.0 && nFrac != 0)
2906 else
2908 char aBuf[100];
2909 sprintf( aBuf, "%.f", fNum ); // simple rounded integer (#100211# - checked)
2910 sStr.appendAscii( aBuf );
2911 impTransliterate(sStr, NumFor[nIx].GetNatNum());
2913 bool bHideFraction = (rInfo.nCntPre > 0 && nFrac == 0
2914 && (sNumeratorFormat.indexOf('0') < 0)
2915 && (sDenominatorFormat.indexOf('0') < 0
2916 || sDenominatorFormat.toInt32() > 0) );
2917 if ( bHideFraction )
2919 sDiv.truncate();
2921 else // if there are some '0' in format, force display of fraction
2923 sFrac = ImpIntToString( nIx, nFrac );
2924 sDiv = ImpIntToString( nIx, nDiv );
2927 sal_uInt16 j = nCnt-1; // Last symbol -> backwards
2928 sal_Int32 k; // Denominator
2930 bRes |= ImpNumberFill(sDiv, fNumber, k, j, nIx, NF_SYMBOLTYPE_FRAC, true);
2932 bool bCont = true;
2933 if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_FRAC)
2935 if ( bHideFraction )
2936 { // do not insert blank for fraction if there is no '?'
2937 if ( sNumeratorFormat.indexOf('?') >= 0
2938 || sDenominatorFormat.indexOf('?') >= 0 )
2939 sDiv.insert(0, ' ');
2941 else
2943 sDiv.insert(0, rInfo.sStrArray[j][0]);
2945 if ( j )
2947 j--;
2949 else
2951 bCont = false;
2954 // Further numerators:
2955 if ( !bCont )
2957 sFrac.truncate();
2959 else
2961 bRes |= ImpNumberFill(sFrac, fNumber, k, j, nIx, NF_SYMBOLTYPE_FRACBLANK);
2962 bCont = false; // there is no integer part?
2963 if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_FRACBLANK)
2965 if ( j )
2967 if ( bHideFraction )
2968 { // '?' in any format force display of blank as delimiter
2969 if ( sIntegerFormat.indexOf('?') >= 0
2970 || sNumeratorFormat.indexOf('?') >= 0
2971 || sDenominatorFormat.indexOf('?') >= 0 )
2973 for (sal_Int32 i = 0; i < rInfo.sStrArray[j].getLength(); i++)
2974 sFrac.insert(0, ' ');
2977 else
2979 if ( fNum != 0.0 || sIntegerFormat.indexOf('0') >= 0 )
2980 sFrac.insert(0, rInfo.sStrArray[j]); // insert Blank string only if there are both integer and fraction
2981 else
2983 if ( sIntegerFormat.indexOf('?') >= 0
2984 || sNumeratorFormat.indexOf('?') >= 0 )
2986 for (sal_Int32 i = 0; i < rInfo.sStrArray[j].getLength(); i++)
2987 sFrac.insert(0, ' ');
2991 j--;
2992 bCont = true; // Yes, there is an integer
2994 else
2995 sFrac.insert(0, rInfo.sStrArray[j]);
2998 // Continue integer part
2999 if ( !bCont )
3001 sStr.truncate();
3003 else
3005 k = sStr.getLength(); // After last figure
3006 bRes |= ImpNumberFillWithThousands(sStr, fNumber, k, j, nIx,
3007 rInfo.nCntPre);
3009 if (bSign && (nFrac != 0 || fNum != 0.0))
3011 sBuff.insert(0, '-'); // Not -0
3013 sBuff.append(sStr);
3014 sBuff.append(sFrac);
3015 sBuff.append(sDiv);
3016 return bRes;
3019 sal_uInt16 SvNumberformat::ImpGetFractionOfSecondString( OUStringBuffer& rBuf, double fFractionOfSecond,
3020 int nFractionDecimals, bool bAddOneRoundingDecimal, sal_uInt16 nIx, sal_uInt16 nMinimumInputLineDecimals )
3022 if (!nFractionDecimals)
3023 return 0;
3025 // nFractionDecimals+1 to not round up what Time::GetClock() carefully
3026 // truncated.
3027 rBuf.append( rtl::math::doubleToUString( fFractionOfSecond, rtl_math_StringFormat_F,
3028 (bAddOneRoundingDecimal ? nFractionDecimals + 1 : nFractionDecimals), '.'));
3029 rBuf.stripStart('0');
3030 rBuf.stripStart('.');
3031 if (bAddOneRoundingDecimal && rBuf.getLength() > nFractionDecimals)
3032 rBuf.truncate( nFractionDecimals); // the digit appended because of nFractionDecimals+1
3033 if (nMinimumInputLineDecimals)
3035 rBuf.stripEnd('0');
3036 for (sal_Int32 index = rBuf.getLength(); index < nMinimumInputLineDecimals; ++index)
3038 rBuf.append('0');
3040 impTransliterate(rBuf, NumFor[nIx].GetNatNum());
3041 nFractionDecimals = rBuf.getLength();
3043 else
3045 impTransliterate(rBuf, NumFor[nIx].GetNatNum());
3047 return static_cast<sal_uInt16>(nFractionDecimals);
3050 bool SvNumberformat::ImpGetTimeOutput(double fNumber,
3051 sal_uInt16 nIx,
3052 OUStringBuffer& sBuff)
3054 using namespace ::com::sun::star::i18n;
3055 bool bCalendarSet = false;
3056 const double fNumberOrig = fNumber;
3057 bool bRes = false;
3058 bool bSign = false;
3059 if (fNumber < 0.0)
3061 fNumber = -fNumber;
3062 if (nIx == 0)
3064 bSign = true;
3067 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
3068 bool bInputLine;
3069 sal_Int32 nCntPost;
3070 if ( rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION &&
3071 0 < rInfo.nCntPost && rInfo.nCntPost < 7 )
3072 { // round at 7 decimals (+5 of 86400 == 12 significant digits)
3073 bInputLine = true;
3074 nCntPost = 7;
3076 else
3078 bInputLine = false;
3079 nCntPost = rInfo.nCntPost;
3082 OUStringBuffer sSecStr;
3083 sal_Int32 nSecPos = 0; // For figure by figure processing
3084 sal_uInt32 nHour, nMin, nSec;
3085 if (!rInfo.bThousand) // No [] format
3087 sal_uInt16 nCHour, nCMinute, nCSecond;
3088 double fFractionOfSecond;
3089 tools::Time::GetClock( fNumberOrig, nCHour, nCMinute, nCSecond, fFractionOfSecond, nCntPost);
3090 nHour = nCHour;
3091 nMin = nCMinute;
3092 nSec = nCSecond;
3093 nCntPost = ImpGetFractionOfSecondString( sSecStr, fFractionOfSecond, nCntPost, true, nIx,
3094 (bInputLine ? rInfo.nCntPost : 0));
3096 else
3098 const double fTime = rtl::math::round( fNumber * 86400.0, int(nCntPost));
3099 if (bSign && fTime == 0.0)
3101 bSign = false; // Not -00:00:00
3103 if (fTime > D_MAX_U_INT32)
3105 sBuff = ImpSvNumberformatScan::sErrStr;
3106 return false;
3108 sal_uInt32 nSeconds = static_cast<sal_uInt32>(fTime);
3110 nCntPost = ImpGetFractionOfSecondString( sSecStr, fTime - nSeconds, nCntPost, false, nIx,
3111 (bInputLine ? rInfo.nCntPost : 0));
3113 if (rInfo.nThousand == 3) // [ss]
3115 nHour = 0;
3116 nMin = 0;
3117 nSec = nSeconds;
3119 else if (rInfo.nThousand == 2) // [mm]:ss
3121 nHour = 0;
3122 nMin = nSeconds / 60;
3123 nSec = nSeconds % 60;
3125 else if (rInfo.nThousand == 1) // [hh]:mm:ss
3127 nHour = nSeconds / 3600;
3128 nMin = (nSeconds%3600) / 60;
3129 nSec = nSeconds%60;
3131 else
3133 // TODO What should these be set to?
3134 nHour = 0;
3135 nMin = 0;
3136 nSec = 0;
3140 sal_Unicode cAmPm = ' '; // a or p
3141 if (rInfo.nCntExp) // AM/PM
3143 if (nHour == 0)
3145 nHour = 12;
3146 cAmPm = 'a';
3148 else if (nHour < 12)
3150 cAmPm = 'a';
3152 else
3154 cAmPm = 'p';
3155 if (nHour > 12)
3157 nHour -= 12;
3161 const sal_uInt16 nCnt = NumFor[nIx].GetCount();
3162 for (sal_uInt16 i = 0; i < nCnt; i++)
3164 sal_Int32 nLen;
3165 switch (rInfo.nTypeArray[i])
3167 case NF_SYMBOLTYPE_STAR:
3168 if( bStarFlag )
3170 bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]);
3172 break;
3173 case NF_SYMBOLTYPE_BLANK:
3174 if (rInfo.sStrArray[i].getLength() >= 2)
3175 InsertBlanks(sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] );
3176 break;
3177 case NF_SYMBOLTYPE_STRING:
3178 case NF_SYMBOLTYPE_CURRENCY:
3179 case NF_SYMBOLTYPE_DATESEP:
3180 case NF_SYMBOLTYPE_TIMESEP:
3181 case NF_SYMBOLTYPE_TIME100SECSEP:
3182 sBuff.append(rInfo.sStrArray[i]);
3183 break;
3184 case NF_SYMBOLTYPE_DIGIT:
3185 nLen = ( bInputLine && i > 0 &&
3186 (rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING ||
3187 rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_TIME100SECSEP) ?
3188 nCntPost : rInfo.sStrArray[i].getLength() );
3189 for (sal_Int32 j = 0; j < nLen && nSecPos < nCntPost && nSecPos < sSecStr.getLength(); ++j)
3191 sBuff.append(sSecStr[nSecPos]);
3192 nSecPos++;
3194 break;
3195 case NF_KEY_AMPM: // AM/PM
3196 if ( !bCalendarSet )
3198 double fDiff = DateTime(rScan.GetNullDate()) - GetCal().getEpochStart();
3199 fDiff += fNumberOrig;
3200 GetCal().setLocalDateTime( fDiff );
3201 bCalendarSet = true;
3203 if (cAmPm == 'a')
3205 sBuff.append(GetCal().getDisplayName(
3206 CalendarDisplayIndex::AM_PM, AmPmValue::AM, 0 ));
3208 else
3210 sBuff.append(GetCal().getDisplayName(
3211 CalendarDisplayIndex::AM_PM, AmPmValue::PM, 0 ));
3213 break;
3214 case NF_KEY_AP: // A/P
3215 if (cAmPm == 'a')
3217 sBuff.append('a');
3219 else
3221 sBuff.append('p');
3223 break;
3224 case NF_KEY_MI: // M
3225 sBuff.append(ImpIntToString( nIx, nMin ));
3226 break;
3227 case NF_KEY_MMI: // MM
3228 sBuff.append(ImpIntToString( nIx, nMin, 2 ));
3229 break;
3230 case NF_KEY_H: // H
3231 sBuff.append(ImpIntToString( nIx, nHour ));
3232 break;
3233 case NF_KEY_HH: // HH
3234 sBuff.append(ImpIntToString( nIx, nHour, 2 ));
3235 break;
3236 case NF_KEY_S: // S
3237 sBuff.append(ImpIntToString( nIx, nSec ));
3238 break;
3239 case NF_KEY_SS: // SS
3240 sBuff.append(ImpIntToString( nIx, nSec, 2 ));
3241 break;
3242 default:
3243 break;
3246 if (bSign && rInfo.bThousand)
3248 sBuff.insert(0, '-');
3250 return bRes;
3254 /** If a day of month occurs within the format, the month name is in possessive
3255 genitive case if the day follows the month, and partitive case if the day
3256 precedes the month. If there is no day of month the nominative case (noun)
3257 is returned. Also if the month is immediately preceded or followed by a
3258 literal string other than space and not followed by a comma, the nominative
3259 name is used, this prevents duplicated casing for MMMM\t\a and such in
3260 documents imported from (e.g. Finnish) Excel or older LibO/OOo releases.
3263 // IDEA: instead of eCodeType pass the index to nTypeArray and restrict
3264 // inspection of month name around that one, that would enable different month
3265 // cases in one format. Though probably the most rare use case ever...
3267 sal_Int32 SvNumberformat::ImpUseMonthCase( int & io_nState, const ImpSvNumFor& rNumFor, NfKeywordIndex eCodeType )
3269 using namespace ::com::sun::star::i18n;
3270 if (!io_nState)
3272 bool bMonthSeen = false;
3273 bool bDaySeen = false;
3274 const ImpSvNumberformatInfo& rInfo = rNumFor.Info();
3275 const sal_uInt16 nCount = rNumFor.GetCount();
3276 for (sal_uInt16 i = 0; i < nCount && io_nState == 0; ++i)
3278 sal_Int32 nLen;
3279 switch (rInfo.nTypeArray[i])
3281 case NF_KEY_D :
3282 case NF_KEY_DD :
3283 if (bMonthSeen)
3285 io_nState = 2;
3287 else
3289 bDaySeen = true;
3291 break;
3292 case NF_KEY_MMM:
3293 case NF_KEY_MMMM:
3294 case NF_KEY_MMMMM:
3295 if ((i < nCount-1 &&
3296 rInfo.nTypeArray[i+1] == NF_SYMBOLTYPE_STRING &&
3297 // Literal following, not empty, space nor comma.
3298 !rInfo.sStrArray[i+1].isEmpty() &&
3299 rInfo.sStrArray[i+1][0] != ' ' && rInfo.sStrArray[i+1][0] != ',') ||
3300 (i > 0 && rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING &&
3301 ((nLen = rInfo.sStrArray[i-1].getLength()) > 0) &&
3302 // Literal preceding, not space.
3303 rInfo.sStrArray[i-1][nLen-1] != ' '))
3305 io_nState = 1;
3307 else if (bDaySeen)
3309 io_nState = 3;
3311 else
3313 bMonthSeen = true;
3315 break;
3318 if (io_nState == 0)
3320 io_nState = 1; // No day of month
3323 switch (io_nState)
3325 case 1:
3326 // No day of month or forced nominative
3327 switch (eCodeType)
3329 case NF_KEY_MMM:
3330 return CalendarDisplayCode::SHORT_MONTH_NAME;
3331 case NF_KEY_MMMM:
3332 return CalendarDisplayCode::LONG_MONTH_NAME;
3333 case NF_KEY_MMMMM:
3334 return CalendarDisplayCode::NARROW_MONTH_NAME;
3335 default:
3336 ; // nothing
3338 break;
3339 case 2:
3340 // Day of month follows month (the month's 17th)
3341 switch (eCodeType)
3343 case NF_KEY_MMM:
3344 return CalendarDisplayCode::SHORT_GENITIVE_MONTH_NAME;
3345 case NF_KEY_MMMM:
3346 return CalendarDisplayCode::LONG_GENITIVE_MONTH_NAME;
3347 case NF_KEY_MMMMM:
3348 return CalendarDisplayCode::NARROW_GENITIVE_MONTH_NAME;
3349 default:
3350 ; // Nothing
3352 break;
3353 case 3:
3354 // Day of month precedes month (17 of month)
3355 switch (eCodeType)
3357 case NF_KEY_MMM:
3358 return CalendarDisplayCode::SHORT_PARTITIVE_MONTH_NAME;
3359 case NF_KEY_MMMM:
3360 return CalendarDisplayCode::LONG_PARTITIVE_MONTH_NAME;
3361 case NF_KEY_MMMMM:
3362 return CalendarDisplayCode::NARROW_PARTITIVE_MONTH_NAME;
3363 default:
3364 ; // nothing
3366 break;
3368 SAL_WARN( "svl.numbers", "ImpUseMonthCase: unhandled keyword index eCodeType");
3369 return CalendarDisplayCode::LONG_MONTH_NAME;
3373 bool SvNumberformat::ImpIsOtherCalendar( const ImpSvNumFor& rNumFor ) const
3375 if ( GetCal().getUniqueID() != GREGORIAN )
3377 return false;
3379 const ImpSvNumberformatInfo& rInfo = rNumFor.Info();
3380 const sal_uInt16 nCnt = rNumFor.GetCount();
3381 sal_uInt16 i;
3382 for ( i = 0; i < nCnt; i++ )
3384 switch ( rInfo.nTypeArray[i] )
3386 case NF_SYMBOLTYPE_CALENDAR :
3387 return false;
3388 case NF_KEY_EC :
3389 case NF_KEY_EEC :
3390 case NF_KEY_R :
3391 case NF_KEY_RR :
3392 case NF_KEY_AAA :
3393 case NF_KEY_AAAA :
3394 case NF_KEY_G :
3395 case NF_KEY_GG :
3396 case NF_KEY_GGG :
3397 return true;
3400 return false;
3403 void SvNumberformat::SwitchToOtherCalendar( OUString& rOrgCalendar,
3404 double& fOrgDateTime ) const
3406 CalendarWrapper& rCal = GetCal();
3407 if ( rCal.getUniqueID() != GREGORIAN )
3408 return;
3410 using namespace ::com::sun::star::i18n;
3411 const css::uno::Sequence< OUString > xCals = rCal.getAllCalendars(
3412 rLoc().getLanguageTag().getLocale() );
3413 sal_Int32 nCnt = xCals.getLength();
3414 if ( nCnt <= 1 )
3415 return;
3417 auto pCal = std::find_if(xCals.begin(), xCals.end(),
3418 [](const OUString& rCalName) { return rCalName != GREGORIAN; });
3419 if (pCal == xCals.end())
3420 return;
3422 if ( !rOrgCalendar.getLength() )
3424 rOrgCalendar = rCal.getUniqueID();
3425 fOrgDateTime = rCal.getDateTime();
3427 rCal.loadCalendar( *pCal, rLoc().getLanguageTag().getLocale() );
3428 rCal.setDateTime( fOrgDateTime );
3431 void SvNumberformat::SwitchToGregorianCalendar( const OUString& rOrgCalendar,
3432 double fOrgDateTime ) const
3434 CalendarWrapper& rCal = GetCal();
3435 if ( rOrgCalendar.getLength() && rCal.getUniqueID() != GREGORIAN )
3437 rCal.loadCalendar( GREGORIAN, rLoc().getLanguageTag().getLocale() );
3438 rCal.setDateTime( fOrgDateTime );
3442 bool SvNumberformat::ImpFallBackToGregorianCalendar( OUString& rOrgCalendar, double& fOrgDateTime )
3444 using namespace ::com::sun::star::i18n;
3445 CalendarWrapper& rCal = GetCal();
3446 if ( rCal.getUniqueID() != GREGORIAN )
3448 sal_Int16 nVal = rCal.getValue( CalendarFieldIndex::ERA );
3449 if ( nVal == 0 && rCal.getLoadedCalendar().Eras[0].ID == "Dummy" )
3451 if ( !rOrgCalendar.getLength() )
3453 rOrgCalendar = rCal.getUniqueID();
3454 fOrgDateTime = rCal.getDateTime();
3456 else if ( rOrgCalendar == GREGORIAN )
3458 rOrgCalendar.clear();
3460 rCal.loadCalendar( GREGORIAN, rLoc().getLanguageTag().getLocale() );
3461 rCal.setDateTime( fOrgDateTime );
3462 return true;
3465 return false;
3469 #ifdef THE_FUTURE
3470 /* XXX NOTE: even if the ImpSwitchToSpecifiedCalendar method is currently
3471 * unused please don't remove it, it would be needed by
3472 * SwitchToSpecifiedCalendar(), see comment in
3473 * ImpSvNumberInputScan::GetDateRef() */
3475 bool SvNumberformat::ImpSwitchToSpecifiedCalendar( OUString& rOrgCalendar,
3476 double& fOrgDateTime,
3477 const ImpSvNumFor& rNumFor ) const
3479 const ImpSvNumberformatInfo& rInfo = rNumFor.Info();
3480 const sal_uInt16 nCnt = rNumFor.GetCount();
3481 for ( sal_uInt16 i = 0; i < nCnt; i++ )
3483 if ( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_CALENDAR )
3485 CalendarWrapper& rCal = GetCal();
3486 if ( !rOrgCalendar.getLength() )
3488 rOrgCalendar = rCal.getUniqueID();
3489 fOrgDateTime = rCal.getDateTime();
3491 rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLocale() );
3492 rCal.setDateTime( fOrgDateTime );
3493 return true;
3496 return false;
3498 #endif
3500 // static
3501 void SvNumberformat::ImpAppendEraG( OUStringBuffer& OutString,
3502 const CalendarWrapper& rCal,
3503 sal_Int16 nNatNum )
3505 using namespace ::com::sun::star::i18n;
3506 if ( rCal.getUniqueID() == "gengou" )
3508 sal_Unicode cEra;
3509 sal_Int16 nVal = rCal.getValue( CalendarFieldIndex::ERA );
3510 switch ( nVal )
3512 case 1:
3513 cEra = 'M';
3514 break;
3515 case 2:
3516 cEra = 'T';
3517 break;
3518 case 3:
3519 cEra = 'S';
3520 break;
3521 case 4:
3522 cEra = 'H';
3523 break;
3524 case 5:
3525 cEra = 'R';
3526 break;
3527 default:
3528 cEra = '?';
3529 break;
3531 OutString.append(cEra);
3533 else
3535 OutString.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum ));
3539 bool SvNumberformat::ImpIsIso8601( const ImpSvNumFor& rNumFor ) const
3541 bool bIsIso = false;
3542 if (eType & SvNumFormatType::DATE)
3544 enum State
3546 eNone,
3547 eAtYear,
3548 eAtSep1,
3549 eAtMonth,
3550 eAtSep2,
3551 eNotIso
3553 State eState = eNone;
3554 auto & rTypeArray = rNumFor.Info().nTypeArray;
3555 sal_uInt16 nCnt = rNumFor.GetCount();
3556 for (sal_uInt16 i=0; i < nCnt && !bIsIso && eState != eNotIso; ++i)
3558 switch ( rTypeArray[i] )
3560 case NF_KEY_YY: // two digits not strictly ISO 8601
3561 case NF_KEY_YYYY:
3562 if (eState != eNone)
3564 eState = eNotIso;
3566 else
3568 eState = eAtYear;
3570 break;
3571 case NF_KEY_M: // single digit not strictly ISO 8601
3572 case NF_KEY_MM:
3573 if (eState != eAtSep1)
3575 eState = eNotIso;
3577 else
3579 eState = eAtMonth;
3581 break;
3582 case NF_KEY_D: // single digit not strictly ISO 8601
3583 case NF_KEY_DD:
3584 if (eState != eAtSep2)
3586 eState = eNotIso;
3588 else
3590 bIsIso = true;
3592 break;
3593 case NF_SYMBOLTYPE_STRING:
3594 case NF_SYMBOLTYPE_DATESEP:
3595 if (rNumFor.Info().sStrArray[i] == "-")
3597 if (eState == eAtYear)
3599 eState = eAtSep1;
3601 else if (eState == eAtMonth)
3603 eState = eAtSep2;
3605 else
3607 eState = eNotIso;
3610 else
3612 eState = eNotIso;
3614 break;
3615 default:
3616 eState = eNotIso;
3620 else
3622 SAL_WARN( "svl.numbers", "SvNumberformat::ImpIsIso8601: no date" );
3624 return bIsIso;
3627 static bool lcl_hasEra( const ImpSvNumFor& rNumFor )
3629 const ImpSvNumberformatInfo& rInfo = rNumFor.Info();
3630 const sal_uInt16 nCnt = rNumFor.GetCount();
3631 for ( sal_uInt16 i = 0; i < nCnt; i++ )
3633 switch ( rInfo.nTypeArray[i] )
3635 case NF_KEY_RR :
3636 case NF_KEY_G :
3637 case NF_KEY_GG :
3638 case NF_KEY_GGG :
3639 return true;
3642 return false;
3645 static bool lcl_isSignedYear( const CalendarWrapper& rCal, const ImpSvNumFor& rNumFor )
3647 return rCal.getValue( css::i18n::CalendarFieldIndex::ERA ) == 0 &&
3648 rCal.getUniqueID() == GREGORIAN && !lcl_hasEra( rNumFor );
3651 /* XXX: if needed this could be stripped from rEpochStart and diff adding and
3652 * moved to tools' DateTime to be reused elsewhere. */
3653 static bool lcl_getValidDate( const DateTime& rNullDate, const DateTime& rEpochStart, double& fNumber )
3655 static const DateTime aCE( Date(1,1,1));
3656 static const DateTime aMin( Date(1,1, SAL_MIN_INT16));
3657 static const DateTime aMax( Date(31,12, SAL_MAX_INT16), tools::Time(23,59,59, tools::Time::nanoSecPerSec - 1));
3658 static const double fMin = aMin - aCE;
3659 static const double fMax = aMax - aCE;
3660 // Value must be representable in our tools::Date proleptic Gregorian
3661 // calendar as well.
3662 const double fOff = (rNullDate - aCE) + fNumber;
3663 // Add diff between epochs to serial date number.
3664 const double fDiff = rNullDate - rEpochStart;
3665 fNumber += fDiff;
3666 return fMin <= fOff && fOff <= fMax;
3669 bool SvNumberformat::ImpGetDateOutput(double fNumber,
3670 sal_uInt16 nIx,
3671 OUStringBuffer& sBuff)
3673 using namespace ::com::sun::star::i18n;
3674 bool bRes = false;
3676 CalendarWrapper& rCal = GetCal();
3677 if (!lcl_getValidDate( rScan.GetNullDate(), rCal.getEpochStart(), fNumber))
3679 sBuff = ImpSvNumberformatScan::sErrStr;
3680 return false;
3682 rCal.setLocalDateTime( fNumber );
3683 int nUseMonthCase = 0; // Not decided yet
3684 OUString aOrgCalendar; // empty => not changed yet
3686 double fOrgDateTime(0.0);
3687 bool bOtherCalendar = ImpIsOtherCalendar( NumFor[nIx] );
3688 if ( bOtherCalendar )
3690 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
3692 if ( ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime ) )
3694 bOtherCalendar = false;
3696 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
3697 const sal_uInt16 nCnt = NumFor[nIx].GetCount();
3698 sal_Int16 nNatNum = NumFor[nIx].GetNatNum().GetNatNum();
3699 OUString aStr;
3701 // NatNum12: if the date format contains more than a date
3702 // field, it needs to specify in NatNum12 argument
3703 // which date element needs special formatting:
3705 // '[NatNum12 ordinal-number]D' -> "1st"
3706 // '[NatNum12 D=ordinal-number]D" of "MMMM' -> "1st of April"
3707 // '[NatNum12 D=ordinal]D" of "MMMM' -> "first of April"
3708 // '[NatNum12 YYYY=year,D=ordinal]D" of "MMMM", "YYYY' -> "first of April, nineteen ninety"
3710 // Note: set only for YYYY, MMMM, M, DDDD, D and NNN/AAAA in date formats.
3711 // Additionally for MMMMM, MMM, DDD and NN/AA to support at least
3712 // capitalize, upper, lower, title.
3713 // XXX It's possible to extend this for other keywords and date + time
3714 // combinations, as required.
3716 bool bUseSpellout = NatNumTakesParameters(nNatNum) &&
3717 (nCnt == 1 || NumFor[nIx].GetNatNum().GetParams().indexOf('=') > -1);
3719 for (sal_uInt16 i = 0; i < nCnt; i++)
3721 switch (rInfo.nTypeArray[i])
3723 case NF_SYMBOLTYPE_CALENDAR :
3724 if ( !aOrgCalendar.getLength() )
3726 aOrgCalendar = rCal.getUniqueID();
3727 fOrgDateTime = rCal.getDateTime();
3729 rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLanguageTag().getLocale() );
3730 rCal.setDateTime( fOrgDateTime );
3731 ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime );
3732 break;
3733 case NF_SYMBOLTYPE_STAR:
3734 if( bStarFlag )
3736 bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]);
3738 break;
3739 case NF_SYMBOLTYPE_BLANK:
3740 if (rInfo.sStrArray[i].getLength() >= 2)
3741 InsertBlanks( sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] );
3742 break;
3743 case NF_SYMBOLTYPE_STRING:
3744 case NF_SYMBOLTYPE_CURRENCY:
3745 case NF_SYMBOLTYPE_DATESEP:
3746 case NF_SYMBOLTYPE_TIMESEP:
3747 case NF_SYMBOLTYPE_TIME100SECSEP:
3748 sBuff.append(rInfo.sStrArray[i]);
3749 break;
3750 case NF_KEY_M: // M
3751 aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_MONTH, nNatNum );
3752 // NatNum12: support variants of preposition, suffixation or article
3753 // for example, Catalan "de març", but "d'abril" etc.
3754 if ( bUseSpellout )
3756 aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]);
3758 sBuff.append(aStr);
3759 break;
3760 case NF_KEY_MM: // MM
3761 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_MONTH, nNatNum ));
3762 break;
3763 case NF_KEY_MMM: // MMM
3764 case NF_KEY_MMMM: // MMMM
3765 case NF_KEY_MMMMM: // MMMMM
3766 // NatNum12: support variants of preposition, suffixation or
3767 // article, or capitalize, upper, lower, title.
3768 // Note: result of the "spell out" conversion can depend from the optional
3769 // PartitiveMonths or GenitiveMonths defined in the locale data,
3770 // see description of ImpUseMonthCase(), and locale data in
3771 // i18npool/source/localedata/data/ and libnumbertext
3772 aStr = rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx],
3773 static_cast<NfKeywordIndex>(rInfo.nTypeArray[i])),
3774 nNatNum);
3775 if ( bUseSpellout )
3777 aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]);
3779 sBuff.append(aStr);
3780 break;
3781 case NF_KEY_Q: // Q
3782 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_QUARTER, nNatNum ));
3783 break;
3784 case NF_KEY_QQ: // QQ
3785 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_QUARTER, nNatNum ));
3786 break;
3787 case NF_KEY_D: // D
3788 aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY, nNatNum );
3789 // NatNum12: support variants of preposition, suffixation or article
3790 if ( bUseSpellout )
3792 aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]);
3794 sBuff.append(aStr);
3795 break;
3796 case NF_KEY_DD: // DD
3797 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY, nNatNum ));
3798 break;
3799 case NF_KEY_DDD: // DDD
3800 if ( bOtherCalendar )
3802 SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime );
3804 aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum );
3805 // NatNum12: support at least capitalize, upper, lower, title
3806 if ( bUseSpellout )
3808 aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]);
3810 sBuff.append(aStr);
3811 if ( bOtherCalendar )
3813 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
3815 break;
3816 case NF_KEY_DDDD: // DDDD
3817 if ( bOtherCalendar )
3819 SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime );
3821 aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum );
3822 // NatNum12: support variants of preposition, suffixation or article
3823 if ( bUseSpellout )
3825 aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]);
3827 sBuff.append(aStr);
3828 if ( bOtherCalendar )
3830 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
3832 break;
3833 case NF_KEY_YY: // YY
3834 if ( bOtherCalendar )
3836 SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime );
3838 // Prepend a minus sign if Gregorian BCE and era is not displayed.
3839 if (lcl_isSignedYear( rCal, NumFor[nIx] ))
3841 sBuff.append('-');
3843 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum ));
3844 if ( bOtherCalendar )
3846 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
3848 break;
3849 case NF_KEY_YYYY: // YYYY
3850 if ( bOtherCalendar )
3852 SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime );
3854 // Prepend a minus sign if Gregorian BCE and era is not displayed.
3855 if (lcl_isSignedYear( rCal, NumFor[nIx] ))
3857 sBuff.append('-');
3859 aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum );
3860 if (aStr.getLength() < 4 && !lcl_hasEra(NumFor[nIx]))
3862 using namespace comphelper::string;
3863 // Ensure that year consists of at least 4 digits, so it
3864 // can be distinguished from 2 digits display and edited
3865 // without suddenly being hit by the 2-digit year magic.
3866 OUStringBuffer aBuf;
3867 padToLength(aBuf, 4 - aStr.getLength(), '0');
3868 impTransliterate(aBuf, NumFor[nIx].GetNatNum());
3869 aBuf.append(aStr);
3870 aStr = aBuf.makeStringAndClear();
3872 // NatNum12: support variants of preposition, suffixation or article
3873 if ( bUseSpellout )
3875 aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]);
3877 sBuff.append(aStr);
3878 if ( bOtherCalendar )
3880 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
3882 break;
3883 case NF_KEY_EC: // E
3884 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum ));
3885 break;
3886 case NF_KEY_EEC: // EE
3887 case NF_KEY_R: // R
3888 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum ));
3889 break;
3890 case NF_KEY_NN: // NN
3891 case NF_KEY_AAA: // AAA
3892 aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum );
3893 // NatNum12: support at least capitalize, upper, lower, title
3894 if ( bUseSpellout )
3896 aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]);
3898 sBuff.append(aStr);
3899 break;
3900 case NF_KEY_NNN: // NNN
3901 case NF_KEY_AAAA: // AAAA
3902 aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum );
3903 // NatNum12: support variants of preposition, suffixation or article
3904 if ( bUseSpellout )
3906 aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]);
3908 sBuff.append(aStr);
3909 break;
3910 case NF_KEY_NNNN: // NNNN
3911 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ));
3912 sBuff.append(rLoc().getLongDateDayOfWeekSep());
3913 break;
3914 case NF_KEY_WW : // WW
3915 sBuff.append(ImpIntToString( nIx,
3916 rCal.getValue( CalendarFieldIndex::WEEK_OF_YEAR )));
3917 break;
3918 case NF_KEY_G: // G
3919 ImpAppendEraG(sBuff, rCal, nNatNum );
3920 break;
3921 case NF_KEY_GG: // GG
3922 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum ));
3923 break;
3924 case NF_KEY_GGG: // GGG
3925 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_ERA, nNatNum ));
3926 break;
3927 case NF_KEY_RR: // RR => GGGEE
3928 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR_AND_ERA, nNatNum ));
3929 break;
3932 if ( aOrgCalendar.getLength() )
3934 rCal.loadCalendar( aOrgCalendar, rLoc().getLanguageTag().getLocale() ); // restore calendar
3936 return bRes;
3939 bool SvNumberformat::ImpGetDateTimeOutput(double fNumber,
3940 sal_uInt16 nIx,
3941 OUStringBuffer& sBuff)
3943 using namespace ::com::sun::star::i18n;
3944 bool bRes = false;
3946 CalendarWrapper& rCal = GetCal();
3947 if (!lcl_getValidDate( rScan.GetNullDate(), rCal.getEpochStart(), fNumber))
3949 sBuff = ImpSvNumberformatScan::sErrStr;
3950 return false;
3953 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
3954 bool bInputLine;
3955 sal_Int32 nCntPost;
3956 if ( rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION &&
3957 0 < rInfo.nCntPost && rInfo.nCntPost < 7 )
3959 // round at 7 decimals (+5 of 86400 == 12 significant digits)
3960 bInputLine = true;
3961 nCntPost = 7;
3963 else
3965 bInputLine = false;
3966 nCntPost = rInfo.nCntPost;
3968 double fTime = (fNumber - floor( fNumber )) * 86400.0;
3969 fTime = ::rtl::math::round( fTime, int(nCntPost) );
3970 if (fTime >= 86400.0)
3972 // result of fNumber==x.999999999... rounded up, use correct date/time
3973 fTime -= 86400.0;
3974 fNumber = floor( fNumber + 0.5) + fTime;
3976 rCal.setLocalDateTime( fNumber );
3978 int nUseMonthCase = 0; // Not decided yet
3979 OUString aOrgCalendar; // empty => not changed yet
3980 double fOrgDateTime(0.0);
3981 bool bOtherCalendar = ImpIsOtherCalendar( NumFor[nIx] );
3982 if ( bOtherCalendar )
3984 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
3986 if ( ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime ) )
3988 bOtherCalendar = false;
3990 sal_Int16 nNatNum = NumFor[nIx].GetNatNum().GetNatNum();
3992 OUStringBuffer sSecStr;
3993 sal_Int32 nSecPos = 0; // For figure by figure processing
3994 sal_uInt32 nHour, nMin, nSec;
3995 if (!rInfo.bThousand) // No [] format
3997 sal_uInt16 nCHour, nCMinute, nCSecond;
3998 double fFractionOfSecond;
3999 tools::Time::GetClock( fNumber, nCHour, nCMinute, nCSecond, fFractionOfSecond, nCntPost);
4000 nHour = nCHour;
4001 nMin = nCMinute;
4002 nSec = nCSecond;
4003 nCntPost = ImpGetFractionOfSecondString( sSecStr, fFractionOfSecond, nCntPost, true, nIx,
4004 (bInputLine ? rInfo.nCntPost : 0));
4006 else
4008 sal_uInt32 nSeconds = static_cast<sal_uInt32>(floor( fTime ));
4010 nCntPost = ImpGetFractionOfSecondString( sSecStr, fTime - nSeconds, nCntPost, false, nIx,
4011 (bInputLine ? rInfo.nCntPost : 0));
4013 if (rInfo.nThousand == 3) // [ss]
4015 nHour = 0;
4016 nMin = 0;
4017 nSec = nSeconds;
4019 else if (rInfo.nThousand == 2) // [mm]:ss
4021 nHour = 0;
4022 nMin = nSeconds / 60;
4023 nSec = nSeconds % 60;
4025 else if (rInfo.nThousand == 1) // [hh]:mm:ss
4027 nHour = nSeconds / 3600;
4028 nMin = (nSeconds%3600) / 60;
4029 nSec = nSeconds%60;
4031 else
4033 nHour = 0; // TODO What should these values be?
4034 nMin = 0;
4035 nSec = 0;
4038 sal_Unicode cAmPm = ' '; // a or p
4039 if (rInfo.nCntExp) // AM/PM
4041 if (nHour == 0)
4043 nHour = 12;
4044 cAmPm = 'a';
4046 else if (nHour < 12)
4048 cAmPm = 'a';
4050 else
4052 cAmPm = 'p';
4053 if (nHour > 12)
4055 nHour -= 12;
4059 const sal_uInt16 nCnt = NumFor[nIx].GetCount();
4060 sal_Int32 nLen;
4061 OUString aYear;
4062 for (sal_uInt16 i = 0; i < nCnt; i++)
4064 switch (rInfo.nTypeArray[i])
4066 case NF_SYMBOLTYPE_CALENDAR :
4067 if ( !aOrgCalendar.getLength() )
4069 aOrgCalendar = rCal.getUniqueID();
4070 fOrgDateTime = rCal.getDateTime();
4072 rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLanguageTag().getLocale() );
4073 rCal.setDateTime( fOrgDateTime );
4074 ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime );
4075 break;
4076 case NF_SYMBOLTYPE_STAR:
4077 if( bStarFlag )
4079 bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]);
4081 break;
4082 case NF_SYMBOLTYPE_BLANK:
4083 if (rInfo.sStrArray[i].getLength() >= 2)
4084 InsertBlanks( sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] );
4085 break;
4086 case NF_SYMBOLTYPE_STRING:
4087 case NF_SYMBOLTYPE_CURRENCY:
4088 case NF_SYMBOLTYPE_DATESEP:
4089 case NF_SYMBOLTYPE_TIMESEP:
4090 case NF_SYMBOLTYPE_TIME100SECSEP:
4091 sBuff.append(rInfo.sStrArray[i]);
4092 break;
4093 case NF_SYMBOLTYPE_DIGIT:
4094 nLen = ( bInputLine && i > 0 &&
4095 (rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING ||
4096 rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_TIME100SECSEP) ?
4097 nCntPost : rInfo.sStrArray[i].getLength() );
4098 for (sal_Int32 j = 0; j < nLen && nSecPos < nCntPost && nSecPos < sSecStr.getLength(); ++j)
4100 sBuff.append(sSecStr[ nSecPos ]);
4101 nSecPos++;
4103 break;
4104 case NF_KEY_AMPM: // AM/PM
4105 if (cAmPm == 'a')
4107 sBuff.append(rCal.getDisplayName( CalendarDisplayIndex::AM_PM,
4108 AmPmValue::AM, 0 ));
4110 else
4112 sBuff.append(rCal.getDisplayName( CalendarDisplayIndex::AM_PM,
4113 AmPmValue::PM, 0 ));
4115 break;
4116 case NF_KEY_AP: // A/P
4117 if (cAmPm == 'a')
4119 sBuff.append('a');
4121 else
4123 sBuff.append('p');
4125 break;
4126 case NF_KEY_MI: // M
4127 sBuff.append(ImpIntToString( nIx, nMin ));
4128 break;
4129 case NF_KEY_MMI: // MM
4130 sBuff.append(ImpIntToString( nIx, nMin, 2 ));
4131 break;
4132 case NF_KEY_H: // H
4133 sBuff.append(ImpIntToString( nIx, nHour ));
4134 break;
4135 case NF_KEY_HH: // HH
4136 sBuff.append(ImpIntToString( nIx, nHour, 2 ));
4137 break;
4138 case NF_KEY_S: // S
4139 sBuff.append(ImpIntToString( nIx, nSec ));
4140 break;
4141 case NF_KEY_SS: // SS
4142 sBuff.append(ImpIntToString( nIx, nSec, 2 ));
4143 break;
4144 case NF_KEY_M: // M
4145 sBuff.append(rCal.getDisplayString(
4146 CalendarDisplayCode::SHORT_MONTH, nNatNum ));
4147 break;
4148 case NF_KEY_MM: // MM
4149 sBuff.append(rCal.getDisplayString(
4150 CalendarDisplayCode::LONG_MONTH, nNatNum ));
4151 break;
4152 case NF_KEY_MMM: // MMM
4153 sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx],
4154 static_cast<NfKeywordIndex>(rInfo.nTypeArray[i])),
4155 nNatNum));
4156 break;
4157 case NF_KEY_MMMM: // MMMM
4158 sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx],
4159 static_cast<NfKeywordIndex>(rInfo.nTypeArray[i])),
4160 nNatNum));
4161 break;
4162 case NF_KEY_MMMMM: // MMMMM
4163 sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx],
4164 static_cast<NfKeywordIndex>(rInfo.nTypeArray[i])),
4165 nNatNum));
4166 break;
4167 case NF_KEY_Q: // Q
4168 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_QUARTER, nNatNum ));
4169 break;
4170 case NF_KEY_QQ: // QQ
4171 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_QUARTER, nNatNum ));
4172 break;
4173 case NF_KEY_D: // D
4174 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY, nNatNum ));
4175 break;
4176 case NF_KEY_DD: // DD
4177 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY, nNatNum ));
4178 break;
4179 case NF_KEY_DDD: // DDD
4180 if ( bOtherCalendar )
4182 SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime );
4184 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum ));
4185 if ( bOtherCalendar )
4187 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
4189 break;
4190 case NF_KEY_DDDD: // DDDD
4191 if ( bOtherCalendar )
4193 SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime );
4195 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ));
4196 if ( bOtherCalendar )
4198 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
4200 break;
4201 case NF_KEY_YY: // YY
4202 if ( bOtherCalendar )
4204 SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime );
4206 // Prepend a minus sign if Gregorian BCE and era is not displayed.
4207 if (lcl_isSignedYear( rCal, NumFor[nIx] ))
4209 sBuff.append('-');
4211 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum ));
4212 if ( bOtherCalendar )
4214 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
4216 break;
4217 case NF_KEY_YYYY: // YYYY
4218 if ( bOtherCalendar )
4220 SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime );
4222 // Prepend a minus sign if Gregorian BCE and era is not displayed.
4223 if (lcl_isSignedYear( rCal, NumFor[nIx] ))
4225 sBuff.append('-');
4227 aYear = rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum );
4228 if (aYear.getLength() < 4 && !lcl_hasEra(NumFor[nIx]))
4230 using namespace comphelper::string;
4231 // Ensure that year consists of at least 4 digits, so it
4232 // can be distinguished from 2 digits display and edited
4233 // without suddenly being hit by the 2-digit year magic.
4234 OUStringBuffer aBuf;
4235 padToLength(aBuf, 4 - aYear.getLength(), '0');
4236 impTransliterate(aBuf, NumFor[nIx].GetNatNum());
4237 aBuf.append(aYear);
4238 sBuff.append(aBuf);
4240 else
4242 sBuff.append(aYear);
4244 if ( bOtherCalendar )
4246 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
4248 break;
4249 case NF_KEY_EC: // E
4250 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum ));
4251 break;
4252 case NF_KEY_EEC: // EE
4253 case NF_KEY_R: // R
4254 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum ));
4255 break;
4256 case NF_KEY_NN: // NN
4257 case NF_KEY_AAA: // AAA
4258 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum ));
4259 break;
4260 case NF_KEY_NNN: // NNN
4261 case NF_KEY_AAAA: // AAAA
4262 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ));
4263 break;
4264 case NF_KEY_NNNN: // NNNN
4265 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ));
4266 sBuff.append(rLoc().getLongDateDayOfWeekSep());
4267 break;
4268 case NF_KEY_WW : // WW
4269 sBuff.append(ImpIntToString( nIx, rCal.getValue( CalendarFieldIndex::WEEK_OF_YEAR )));
4270 break;
4271 case NF_KEY_G: // G
4272 ImpAppendEraG( sBuff, rCal, nNatNum );
4273 break;
4274 case NF_KEY_GG: // GG
4275 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum ));
4276 break;
4277 case NF_KEY_GGG: // GGG
4278 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_ERA, nNatNum ));
4279 break;
4280 case NF_KEY_RR: // RR => GGGEE
4281 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR_AND_ERA, nNatNum ));
4282 break;
4285 if ( aOrgCalendar.getLength() )
4287 rCal.loadCalendar( aOrgCalendar, rLoc().getLanguageTag().getLocale() ); // restore calendar
4289 return bRes;
4292 bool SvNumberformat::ImpGetLogicalOutput(double fNumber,
4293 sal_uInt16 nIx,
4294 OUStringBuffer& sStr)
4296 bool bRes = false;
4297 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
4298 const sal_uInt16 nCnt = NumFor[nIx].GetCount();
4299 for (sal_uInt16 j = 0; j < nCnt; ++j)
4301 switch (rInfo.nTypeArray[j])
4303 case NF_KEY_BOOLEAN:
4304 sStr.append( fNumber ? rScan.GetTrueString() : rScan.GetFalseString());
4305 break;
4306 case NF_SYMBOLTYPE_STRING:
4307 sStr.append( rInfo.sStrArray[j]);
4308 break;
4311 impTransliterate(sStr, NumFor[nIx].GetNatNum());
4312 return bRes;
4315 bool SvNumberformat::ImpGetNumberOutput(double fNumber,
4316 sal_uInt16 nIx,
4317 OUStringBuffer& sStr)
4319 bool bRes = false;
4320 bool bSign;
4321 if (fNumber < 0.0)
4323 bSign = (nIx == 0); // Not in the ones at the back;
4324 fNumber = -fNumber;
4326 else
4328 bSign = false;
4329 if ( std::signbit( fNumber ) )
4331 fNumber = -fNumber; // yes, -0.0 is possible, eliminate '-'
4334 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
4335 if (rInfo.eScannedType == SvNumFormatType::PERCENT)
4337 if (fNumber < D_MAX_D_BY_100)
4339 fNumber *= 100.0;
4341 else
4343 sStr = ImpSvNumberformatScan::sErrStr;
4344 return false;
4347 sal_uInt16 i, j;
4348 sal_Int32 nDecPos = -1;
4349 bool bInteger = false;
4350 if ( rInfo.nThousand != FLAG_STANDARD_IN_FORMAT )
4352 // Special formatting only if no GENERAL keyword in format code
4353 const sal_uInt16 nThousand = rInfo.nThousand;
4354 tools::Long nPrecExp;
4355 for (i = 0; i < nThousand; i++)
4357 if (fNumber > D_MIN_M_BY_1000)
4359 fNumber /= 1000.0;
4361 else
4363 fNumber = 0.0;
4366 if (fNumber > 0.0)
4368 nPrecExp = GetPrecExp( fNumber );
4370 else
4372 nPrecExp = 0;
4374 if (rInfo.nCntPost) // Decimal places
4376 if ((rInfo.nCntPost + nPrecExp) > 15 && nPrecExp < 15)
4378 sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, 15-nPrecExp, '.');
4379 for (tools::Long l = 15-nPrecExp; l < static_cast<tools::Long>(rInfo.nCntPost); l++)
4381 sStr.append('0');
4384 else
4386 sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, rInfo.nCntPost, '.' );
4388 sStr.stripStart('0'); // Strip leading zeros
4390 else if (fNumber == 0.0) // Null
4392 // Nothing to be done here, keep empty string sStr,
4393 // ImpNumberFillWithThousands does the rest
4395 else // Integer
4397 sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, 0, '.');
4398 sStr.stripStart('0'); // Strip leading zeros
4400 nDecPos = sStr.indexOf('.' );
4401 if ( nDecPos >= 0)
4403 const sal_Unicode* p = sStr.getStr() + nDecPos;
4404 while ( *++p == '0' )
4406 if ( !*p )
4408 bInteger = true;
4410 sStr.remove( nDecPos, 1 ); // Remove .
4412 if (bSign && (sStr.isEmpty() || checkForAll0s(sStr))) // Only 00000
4414 bSign = false; // Not -0.00
4416 } // End of != FLAG_STANDARD_IN_FORMAT
4418 // Edit backwards:
4419 j = NumFor[nIx].GetCount()-1; // Last symbol
4420 // Decimal places:
4421 bRes |= ImpDecimalFill( sStr, fNumber, nDecPos, j, nIx, bInteger );
4422 if (bSign)
4424 sStr.insert(0, '-');
4426 impTransliterate(sStr, NumFor[nIx].GetNatNum());
4427 return bRes;
4430 bool SvNumberformat::ImpDecimalFill( OUStringBuffer& sStr, // number string
4431 double& rNumber, // number
4432 sal_Int32 nDecPos, // decimals start
4433 sal_uInt16 j, // symbol index within format code
4434 sal_uInt16 nIx, // subformat index
4435 bool bInteger) // is integer
4437 bool bRes = false;
4438 bool bFilled = false; // Was filled?
4439 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
4440 sal_Int32 k = sStr.getLength(); // After last figure
4441 // Decimal places:
4442 if (rInfo.nCntPost > 0)
4444 bool bTrailing = true; // Trailing zeros?
4445 short nType;
4446 while (j > 0 && // Backwards
4447 (nType = rInfo.nTypeArray[j]) != NF_SYMBOLTYPE_DECSEP)
4449 switch ( nType )
4451 case NF_SYMBOLTYPE_STAR:
4452 if( bStarFlag )
4454 bRes = lcl_insertStarFillChar( sStr, k, rInfo.sStrArray[j]);
4456 break;
4457 case NF_SYMBOLTYPE_BLANK:
4458 if (rInfo.sStrArray[j].getLength() >= 2)
4459 /*k = */ InsertBlanks(sStr, k, rInfo.sStrArray[j][1] );
4460 break;
4461 case NF_SYMBOLTYPE_STRING:
4462 case NF_SYMBOLTYPE_CURRENCY:
4463 case NF_SYMBOLTYPE_PERCENT:
4464 sStr.insert(k, rInfo.sStrArray[j]);
4465 break;
4466 case NF_SYMBOLTYPE_THSEP:
4467 if (rInfo.nThousand == 0)
4469 sStr.insert(k, rInfo.sStrArray[j]);
4471 break;
4472 case NF_SYMBOLTYPE_DIGIT:
4474 const OUString& rStr = rInfo.sStrArray[j];
4475 const sal_Unicode* p1 = rStr.getStr();
4476 const sal_Unicode* p = p1 + rStr.getLength();
4477 // In case the number of decimals passed are less than the
4478 // "digits" given, append trailing '0' characters, which here
4479 // means insert them because literal strings may have been
4480 // appended already. If they weren't to be '0' characters
4481 // they'll be changed below, as if decimals with trailing zeros
4482 // were passed.
4483 if (nDecPos >= 0 && nDecPos <= k)
4485 sal_Int32 nAppend = rStr.getLength() - (k - nDecPos);
4486 while (nAppend-- > 0)
4488 sStr.insert( k++, '0');
4491 while (k && p1 < p--)
4493 const sal_Unicode c = *p;
4494 k--;
4495 if ( sStr[k] != '0' )
4497 bTrailing = false;
4498 bFilled = true;
4500 if (bTrailing)
4502 if ( c == '0' )
4504 bFilled = true;
4506 else if ( c == '-' )
4508 if ( bInteger )
4510 sStr[ k ] = '-';
4512 bFilled = true;
4514 else if ( c == '?' )
4516 sStr[ k ] = ' ';
4517 bFilled = true;
4519 else if ( !bFilled ) // #
4521 sStr.remove(k,1);
4524 } // of for
4525 break;
4526 } // of case digi
4527 case NF_KEY_CCC: // CCC currency
4528 sStr.insert(k, rScan.GetCurAbbrev());
4529 break;
4530 case NF_KEY_GENERAL: // Standard in the String
4532 OUStringBuffer sNum;
4533 ImpGetOutputStandard(rNumber, sNum);
4534 sNum.stripStart('-');
4535 sStr.insert(k, sNum.makeStringAndClear());
4536 break;
4538 default:
4539 break;
4540 } // of switch
4541 j--;
4542 } // of while
4543 } // of decimal places
4545 bRes |= ImpNumberFillWithThousands(sStr, rNumber, k, j, nIx, // Fill with . if needed
4546 rInfo.nCntPre, bFilled );
4548 return bRes;
4551 bool SvNumberformat::ImpNumberFillWithThousands( OUStringBuffer& sBuff, // number string
4552 double& rNumber, // number
4553 sal_Int32 k, // position within string
4554 sal_uInt16 j, // symbol index within format code
4555 sal_uInt16 nIx, // subformat index
4556 sal_Int32 nDigCnt, // count of integer digits in format
4557 bool bAddDecSep) // add decimal separator if necessary
4559 bool bRes = false;
4560 sal_Int32 nLeadingStringChars = 0; // inserted StringChars before number
4561 sal_Int32 nDigitCount = 0; // count of integer digits from the right
4562 bool bStop = false;
4563 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
4564 // no normal thousands separators if number divided by thousands
4565 bool bDoThousands = (rInfo.nThousand == 0);
4566 utl::DigitGroupingIterator aGrouping( GetFormatter().GetLocaleData()->getDigitGrouping());
4568 while (!bStop) // backwards
4570 if (j == 0)
4572 bStop = true;
4574 switch (rInfo.nTypeArray[j])
4576 case NF_SYMBOLTYPE_DECSEP:
4577 aGrouping.reset();
4578 [[fallthrough]];
4579 case NF_SYMBOLTYPE_STRING:
4580 case NF_SYMBOLTYPE_CURRENCY:
4581 case NF_SYMBOLTYPE_PERCENT:
4582 if ( rInfo.nTypeArray[j] != NF_SYMBOLTYPE_DECSEP || bAddDecSep )
4583 sBuff.insert(k, rInfo.sStrArray[j]);
4584 if ( k == 0 )
4586 nLeadingStringChars = nLeadingStringChars + rInfo.sStrArray[j].getLength();
4588 break;
4589 case NF_SYMBOLTYPE_STAR:
4590 if( bStarFlag )
4592 bRes = lcl_insertStarFillChar( sBuff, k, rInfo.sStrArray[j]);
4594 break;
4595 case NF_SYMBOLTYPE_BLANK:
4596 if (rInfo.sStrArray[j].getLength() >= 2)
4597 /*k = */ InsertBlanks(sBuff, k, rInfo.sStrArray[j][1] );
4598 break;
4599 case NF_SYMBOLTYPE_THSEP:
4600 // #i7284# #102685# Insert separator also if number is divided
4601 // by thousands and the separator is specified somewhere in
4602 // between and not only at the end.
4603 // #i12596# But do not insert if it's a parenthesized negative
4604 // format like (#,)
4605 // In fact, do not insert if divided and regex [0#,],[^0#] and
4606 // no other digit symbol follows (which was already detected
4607 // during scan of format code, otherwise there would be no
4608 // division), else do insert. Same in ImpNumberFill() below.
4609 if ( !bDoThousands && j < NumFor[nIx].GetCount()-1 )
4611 bDoThousands = ((j == 0) ||
4612 (rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_DIGIT &&
4613 rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_THSEP) ||
4614 (rInfo.nTypeArray[j+1] == NF_SYMBOLTYPE_DIGIT));
4616 if ( bDoThousands )
4618 if (k > 0)
4620 sBuff.insert(k, rInfo.sStrArray[j]);
4622 else if (nDigitCount < nDigCnt)
4624 // Leading '#' displays nothing (e.g. no leading
4625 // separator for numbers <1000 with #,##0 format).
4626 // Leading '?' displays blank.
4627 // Everything else, including nothing, displays the
4628 // separator.
4629 sal_Unicode cLeader = 0;
4630 if (j > 0 && rInfo.nTypeArray[j-1] == NF_SYMBOLTYPE_DIGIT)
4632 const OUString& rStr = rInfo.sStrArray[j-1];
4633 sal_Int32 nLen = rStr.getLength();
4634 if (nLen)
4636 cLeader = rStr[ nLen - 1 ];
4639 switch (cLeader)
4641 case '#':
4642 ; // nothing
4643 break;
4644 case '?':
4645 // replace thousand separator with blank
4646 sBuff.insert(k, ' ');
4647 break;
4648 default:
4649 sBuff.insert(k, rInfo.sStrArray[j]);
4652 aGrouping.advance();
4654 break;
4655 case NF_SYMBOLTYPE_DIGIT:
4657 const OUString& rStr = rInfo.sStrArray[j];
4658 const sal_Unicode* p1 = rStr.getStr();
4659 const sal_Unicode* p = p1 + rStr.getLength();
4660 while ( p1 < p-- )
4662 nDigitCount++;
4663 if (k > 0)
4665 k--;
4667 else
4669 switch (*p)
4671 case '0':
4672 sBuff.insert(0, '0');
4673 break;
4674 case '?':
4675 sBuff.insert(0, ' ');
4676 break;
4679 if (nDigitCount == nDigCnt && k > 0)
4681 // more digits than specified
4682 ImpDigitFill(sBuff, 0, k, nIx, nDigitCount, aGrouping);
4685 break;
4687 case NF_KEY_CCC: // CCC currency
4688 sBuff.insert(k, rScan.GetCurAbbrev());
4689 break;
4690 case NF_KEY_GENERAL: // "General" in string
4692 OUStringBuffer sNum;
4693 ImpGetOutputStandard(rNumber, sNum);
4694 sNum.stripStart('-');
4695 sBuff.insert(k, sNum.makeStringAndClear());
4696 break;
4698 default:
4699 break;
4700 } // switch
4701 j--; // next format code string
4702 } // while
4704 k = k + nLeadingStringChars; // MSC converts += to int and then warns, so ...
4705 if (k > nLeadingStringChars)
4707 ImpDigitFill(sBuff, nLeadingStringChars, k, nIx, nDigitCount, aGrouping);
4709 return bRes;
4712 void SvNumberformat::ImpDigitFill(OUStringBuffer& sStr, // number string
4713 sal_Int32 nStart, // start of digits
4714 sal_Int32 & k, // position within string
4715 sal_uInt16 nIx, // subformat index
4716 sal_Int32 & nDigitCount, // count of integer digits from the right so far
4717 utl::DigitGroupingIterator & rGrouping ) // current grouping
4719 if (NumFor[nIx].Info().bThousand) // Only if grouping fill in separators
4721 const OUString& rThousandSep = GetFormatter().GetNumThousandSep();
4722 while (k > nStart)
4724 if (nDigitCount == rGrouping.getPos())
4726 sStr.insert( k, rThousandSep );
4727 rGrouping.advance();
4729 nDigitCount++;
4730 k--;
4733 else // simply skip
4735 k = nStart;
4739 bool SvNumberformat::ImpNumberFill( OUStringBuffer& sBuff, // number string
4740 double& rNumber, // number for "General" format
4741 sal_Int32& k, // position within string
4742 sal_uInt16& j, // symbol index within format code
4743 sal_uInt16 nIx, // subformat index
4744 short eSymbolType, // type of stop condition
4745 bool bInsertRightBlank)// insert blank on right for denominator (default = false)
4747 bool bRes = false;
4748 bool bStop = false;
4749 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
4750 // no normal thousands separators if number divided by thousands
4751 bool bDoThousands = (rInfo.nThousand == 0);
4752 bool bFoundNumber = false;
4753 short nType;
4755 k = sBuff.getLength(); // behind last digit
4757 while (!bStop && (nType = rInfo.nTypeArray[j]) != eSymbolType ) // Backwards
4759 switch ( nType )
4761 case NF_SYMBOLTYPE_STAR:
4762 if( bStarFlag )
4764 if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP )
4765 k = 0; // tdf#100842 jump to beginning of number before inserting something else
4766 bRes = lcl_insertStarFillChar( sBuff, k, rInfo.sStrArray[j]);
4768 break;
4769 case NF_SYMBOLTYPE_BLANK:
4770 if (rInfo.sStrArray[j].getLength() >= 2)
4772 if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP )
4773 k = 0; // tdf#100842 jump to beginning of number before inserting something else
4774 k = InsertBlanks(sBuff, k, rInfo.sStrArray[j][1] );
4776 break;
4777 case NF_SYMBOLTYPE_THSEP:
4778 // Same as in ImpNumberFillWithThousands() above, do not insert
4779 // if divided and regex [0#,],[^0#] and no other digit symbol
4780 // follows (which was already detected during scan of format
4781 // code, otherwise there would be no division), else do insert.
4782 if ( !bDoThousands && j < NumFor[nIx].GetCount()-1 )
4784 bDoThousands = ((j == 0) ||
4785 (rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_DIGIT &&
4786 rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_THSEP) ||
4787 (rInfo.nTypeArray[j+1] == NF_SYMBOLTYPE_DIGIT));
4789 if ( bDoThousands && k > 0 )
4791 sBuff.insert(k, rInfo.sStrArray[j]);
4793 break;
4794 case NF_SYMBOLTYPE_DIGIT:
4796 bFoundNumber = true;
4797 sal_uInt16 nPosInsertBlank = bInsertRightBlank ? k : 0; // left alignment of denominator
4798 const OUString& rStr = rInfo.sStrArray[j];
4799 const sal_Unicode* p1 = rStr.getStr();
4800 const sal_Unicode* p = p1 + rStr.getLength();
4801 while ( p1 < p-- )
4803 if (k > 0)
4805 k--;
4807 else
4809 switch (*p)
4811 case '0':
4812 sBuff.insert(0, '0');
4813 break;
4814 case '?':
4815 sBuff.insert(nPosInsertBlank, ' ');
4816 break;
4821 break;
4822 case NF_KEY_CCC: // CCC currency
4823 sBuff.insert(k, rScan.GetCurAbbrev());
4824 break;
4825 case NF_KEY_GENERAL: // Standard in the String
4827 OUStringBuffer sNum;
4828 bFoundNumber = true;
4829 ImpGetOutputStandard(rNumber, sNum);
4830 sNum.stripStart('-');
4831 sBuff.insert(k, sNum.makeStringAndClear());
4833 break;
4834 case NF_SYMBOLTYPE_FRAC_FDIV: // Do Nothing
4835 if (k > 0)
4837 k--;
4839 break;
4841 default:
4842 if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP )
4843 k = 0; // tdf#100842 jump to beginning of number before inserting something else
4844 sBuff.insert(k, rInfo.sStrArray[j]);
4845 break;
4846 } // of switch
4847 if ( j )
4848 j--; // Next String
4849 else
4850 bStop = true;
4851 } // of while
4852 return bRes;
4855 void SvNumberformat::GetFormatSpecialInfo(bool& bThousand,
4856 bool& IsRed,
4857 sal_uInt16& nPrecision,
4858 sal_uInt16& nLeadingCnt) const
4860 // as before: take info from nNumFor=0 for whole format (for dialog etc.)
4862 SvNumFormatType nDummyType;
4863 GetNumForInfo( 0, nDummyType, bThousand, nPrecision, nLeadingCnt );
4865 // "negative in red" is only useful for the whole format
4867 const Color* pColor = NumFor[1].GetColor();
4868 IsRed = fLimit1 == 0.0 && fLimit2 == 0.0 && pColor
4869 && (*pColor == ImpSvNumberformatScan::GetRedColor());
4872 void SvNumberformat::GetNumForInfo( sal_uInt16 nNumFor, SvNumFormatType& rScannedType,
4873 bool& bThousand, sal_uInt16& nPrecision, sal_uInt16& nLeadingCnt ) const
4875 // take info from a specified sub-format (for XML export)
4877 if ( nNumFor > 3 )
4879 return; // invalid
4882 const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info();
4883 rScannedType = rInfo.eScannedType;
4884 bThousand = rInfo.bThousand;
4885 nPrecision = (rInfo.eScannedType == SvNumFormatType::FRACTION)
4886 ? rInfo.nCntExp // number of denominator digits for fraction
4887 : rInfo.nCntPost;
4888 sal_Int32 nPosHash = 1;
4889 if ( rInfo.eScannedType == SvNumFormatType::FRACTION &&
4890 ( (nPosHash += GetDenominatorString(nNumFor).indexOf('#')) > 0 ) )
4891 nPrecision -= nPosHash;
4892 if (bStandard && rInfo.eScannedType == SvNumFormatType::NUMBER)
4894 // StandardFormat
4895 nLeadingCnt = 1;
4897 else
4899 nLeadingCnt = 0;
4900 bool bStop = false;
4901 sal_uInt16 i = 0;
4902 const sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
4903 while (!bStop && i < nCnt)
4905 short nType = rInfo.nTypeArray[i];
4906 if ( nType == NF_SYMBOLTYPE_DIGIT)
4908 const sal_Unicode* p = rInfo.sStrArray[i].getStr();
4909 while ( *p == '#' )
4911 p++;
4913 while ( *p++ == '0' )
4915 nLeadingCnt++;
4918 else if (nType == NF_SYMBOLTYPE_DECSEP
4919 || nType == NF_SYMBOLTYPE_EXP
4920 || nType == NF_SYMBOLTYPE_FRACBLANK) // Fraction: stop after integer part,
4921 { // do not count '0' of fraction
4922 bStop = true;
4924 i++;
4929 const OUString* SvNumberformat::GetNumForString( sal_uInt16 nNumFor, sal_uInt16 nPos,
4930 bool bString /* = false */ ) const
4932 if ( nNumFor > 3 )
4934 return nullptr;
4936 sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
4937 if ( !nCnt )
4939 return nullptr;
4941 if ( nPos == 0xFFFF )
4943 nPos = nCnt - 1;
4944 if ( bString )
4945 { // Backwards
4946 short const * pType = NumFor[nNumFor].Info().nTypeArray.data() + nPos;
4947 while ( nPos > 0 && (*pType != NF_SYMBOLTYPE_STRING) &&
4948 (*pType != NF_SYMBOLTYPE_CURRENCY) )
4950 pType--;
4951 nPos--;
4953 if ( (*pType != NF_SYMBOLTYPE_STRING) && (*pType != NF_SYMBOLTYPE_CURRENCY) )
4955 return nullptr;
4959 else if ( nPos > nCnt - 1 )
4961 return nullptr;
4963 else if ( bString )
4965 // forward
4966 short const * pType = NumFor[nNumFor].Info().nTypeArray.data() + nPos;
4967 while ( nPos < nCnt && (*pType != NF_SYMBOLTYPE_STRING) &&
4968 (*pType != NF_SYMBOLTYPE_CURRENCY) )
4970 pType++;
4971 nPos++;
4973 if ( nPos >= nCnt || ((*pType != NF_SYMBOLTYPE_STRING) &&
4974 (*pType != NF_SYMBOLTYPE_CURRENCY)) )
4976 return nullptr;
4979 return &NumFor[nNumFor].Info().sStrArray[nPos];
4982 short SvNumberformat::GetNumForType( sal_uInt16 nNumFor, sal_uInt16 nPos ) const
4984 if ( nNumFor > 3 )
4986 return 0;
4988 sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
4989 if ( !nCnt )
4991 return 0;
4993 if ( nPos == 0xFFFF )
4995 nPos = nCnt - 1;
4997 else if ( nPos > nCnt - 1 )
4999 return 0;
5001 return NumFor[nNumFor].Info().nTypeArray[nPos];
5004 bool SvNumberformat::IsNegativeWithoutSign() const
5006 if ( IsSecondSubformatRealNegative() )
5008 const OUString* pStr = GetNumForString( 1, 0, true );
5009 if ( pStr )
5011 return !HasStringNegativeSign( *pStr );
5014 return false;
5017 bool SvNumberformat::IsNegativeInBracket() const
5019 sal_uInt16 nCnt = NumFor[1].GetCount();
5020 if (!nCnt)
5022 return false;
5024 auto& tmp = NumFor[1].Info().sStrArray;
5025 return tmp[0] == "(" && tmp[nCnt-1] == ")";
5028 bool SvNumberformat::HasPositiveBracketPlaceholder() const
5030 sal_uInt16 nCnt = NumFor[0].GetCount();
5031 return NumFor[0].Info().sStrArray[nCnt-1] == "_)";
5034 DateOrder SvNumberformat::GetDateOrder() const
5036 if ( eType & SvNumFormatType::DATE )
5038 auto& rTypeArray = NumFor[0].Info().nTypeArray;
5039 sal_uInt16 nCnt = NumFor[0].GetCount();
5040 for ( sal_uInt16 j=0; j<nCnt; j++ )
5042 switch ( rTypeArray[j] )
5044 case NF_KEY_D :
5045 case NF_KEY_DD :
5046 return DateOrder::DMY;
5047 case NF_KEY_M :
5048 case NF_KEY_MM :
5049 case NF_KEY_MMM :
5050 case NF_KEY_MMMM :
5051 case NF_KEY_MMMMM :
5052 return DateOrder::MDY;
5053 case NF_KEY_YY :
5054 case NF_KEY_YYYY :
5055 case NF_KEY_EC :
5056 case NF_KEY_EEC :
5057 case NF_KEY_R :
5058 case NF_KEY_RR :
5059 return DateOrder::YMD;
5063 else
5065 SAL_WARN( "svl.numbers", "SvNumberformat::GetDateOrder: no date" );
5067 return rLoc().getDateOrder();
5070 sal_uInt32 SvNumberformat::GetExactDateOrder() const
5072 sal_uInt32 nRet = 0;
5073 if ( !(eType & SvNumFormatType::DATE) )
5075 SAL_WARN( "svl.numbers", "SvNumberformat::GetExactDateOrder: no date" );
5076 return nRet;
5078 auto& rTypeArray = NumFor[0].Info().nTypeArray;
5079 sal_uInt16 nCnt = NumFor[0].GetCount();
5080 int nShift = 0;
5081 for ( sal_uInt16 j=0; j<nCnt && nShift < 3; j++ )
5083 switch ( rTypeArray[j] )
5085 case NF_KEY_D :
5086 case NF_KEY_DD :
5087 nRet = (nRet << 8) | 'D';
5088 ++nShift;
5089 break;
5090 case NF_KEY_M :
5091 case NF_KEY_MM :
5092 case NF_KEY_MMM :
5093 case NF_KEY_MMMM :
5094 case NF_KEY_MMMMM :
5095 nRet = (nRet << 8) | 'M';
5096 ++nShift;
5097 break;
5098 case NF_KEY_YY :
5099 case NF_KEY_YYYY :
5100 case NF_KEY_EC :
5101 case NF_KEY_EEC :
5102 case NF_KEY_R :
5103 case NF_KEY_RR :
5104 nRet = (nRet << 8) | 'Y';
5105 ++nShift;
5106 break;
5109 return nRet;
5112 void SvNumberformat::GetConditions( SvNumberformatLimitOps& rOper1, double& rVal1,
5113 SvNumberformatLimitOps& rOper2, double& rVal2 ) const
5115 rOper1 = eOp1;
5116 rOper2 = eOp2;
5117 rVal1 = fLimit1;
5118 rVal2 = fLimit2;
5121 const Color* SvNumberformat::GetColor( sal_uInt16 nNumFor ) const
5123 if ( nNumFor > 3 )
5125 return nullptr;
5127 return NumFor[nNumFor].GetColor();
5130 static void lcl_SvNumberformat_AddLimitStringImpl( OUString& rStr,
5131 SvNumberformatLimitOps eOp,
5132 double fLimit, std::u16string_view rDecSep )
5134 if ( eOp == NUMBERFORMAT_OP_NO )
5135 return;
5137 switch ( eOp )
5139 case NUMBERFORMAT_OP_EQ :
5140 rStr = "[=";
5141 break;
5142 case NUMBERFORMAT_OP_NE :
5143 rStr = "[<>";
5144 break;
5145 case NUMBERFORMAT_OP_LT :
5146 rStr = "[<";
5147 break;
5148 case NUMBERFORMAT_OP_LE :
5149 rStr = "[<=";
5150 break;
5151 case NUMBERFORMAT_OP_GT :
5152 rStr = "[>";
5153 break;
5154 case NUMBERFORMAT_OP_GE :
5155 rStr = "[>=";
5156 break;
5157 default:
5158 SAL_WARN( "svl.numbers", "unsupported number format" );
5159 break;
5161 rStr += ::rtl::math::doubleToUString( fLimit,
5162 rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max,
5163 rDecSep[0], true);
5164 rStr += "]";
5167 static void lcl_insertLCID( OUStringBuffer& rFormatStr, sal_uInt32 nLCID, sal_Int32 nPosInsertLCID, bool bDBNumInserted )
5169 if ( nLCID == 0 )
5170 return;
5171 if (nPosInsertLCID == rFormatStr.getLength() && !bDBNumInserted)
5172 // No format code, no locale.
5173 return;
5175 OUStringBuffer aLCIDString = OUString::number( nLCID , 16 ).toAsciiUpperCase();
5176 // Search for only last DBNum which is the last element before insertion position
5177 if ( bDBNumInserted && nPosInsertLCID >= 8
5178 && aLCIDString.getLength() > 4
5179 && rFormatStr.indexOf( "[DBNum", nPosInsertLCID-8) == nPosInsertLCID-8 )
5180 { // remove DBNumX code if long LCID
5181 nPosInsertLCID -= 8;
5182 rFormatStr.remove( nPosInsertLCID, 8 );
5184 aLCIDString.insert( 0, "[$-" );
5185 aLCIDString.append( "]" );
5186 rFormatStr.insert( nPosInsertLCID, aLCIDString.toString() );
5189 /** Increment nAlphabetID for CJK numerals
5190 * +1 for financial numerals [NatNum2]
5191 * +2 for Arabic fullwidth numerals [NatNum3]
5192 * */
5193 static void lcl_incrementAlphabetWithNatNum ( sal_uInt32& nAlphabetID, sal_uInt32 nNatNum )
5195 if ( nNatNum == 2) // financial
5196 nAlphabetID += 1;
5197 else if ( nNatNum == 3)
5198 nAlphabetID += 2;
5199 nAlphabetID = nAlphabetID << 24;
5202 OUString SvNumberformat::GetMappedFormatstring( const NfKeywordTable& rKeywords,
5203 const LocaleDataWrapper& rLocWrp,
5204 LanguageType nOriginalLang /* =LANGUAGE_DONTKNOW */,
5205 bool bSystemLanguage /* =false */ ) const
5207 OUStringBuffer aStr;
5208 if (maLocale.meSubstitute != LocaleType::Substitute::NONE)
5210 // XXX: theoretically this could clash with the first subformat's
5211 // lcl_insertLCID() below, in practice as long as it is used for system
5212 // time and date modifiers it shouldn't (i.e. there is no calendar or
5213 // numeral specified as well).
5214 aStr.append("[$-" + maLocale.generateCode() + "]");
5216 bool bDefault[4];
5217 // 1 subformat matches all if no condition specified,
5218 bDefault[0] = ( NumFor[1].GetCount() == 0 && eOp1 == NUMBERFORMAT_OP_NO );
5219 // with 2 subformats [>=0];[<0] is implied if no condition specified
5220 bDefault[1] = ( !bDefault[0] && NumFor[2].GetCount() == 0 &&
5221 eOp1 == NUMBERFORMAT_OP_GE && fLimit1 == 0.0 &&
5222 eOp2 == NUMBERFORMAT_OP_NO && fLimit2 == 0.0 );
5223 // with 3 or more subformats [>0];[<0];[=0] is implied if no condition specified,
5224 // note that subformats may be empty (;;;) and NumFor[2].GetCount()>0 is not checked.
5225 bDefault[2] = ( !bDefault[0] && !bDefault[1] &&
5226 eOp1 == NUMBERFORMAT_OP_GT && fLimit1 == 0.0 &&
5227 eOp2 == NUMBERFORMAT_OP_LT && fLimit2 == 0.0 );
5228 bool bDefaults = bDefault[0] || bDefault[1] || bDefault[2];
5229 // from now on bDefault[] values are used to append empty subformats at the end
5230 bDefault[3] = false;
5231 if ( !bDefaults )
5233 // conditions specified
5234 if ( eOp1 != NUMBERFORMAT_OP_NO && eOp2 == NUMBERFORMAT_OP_NO )
5236 bDefault[0] = bDefault[1] = true; // [];x
5238 else if ( eOp1 != NUMBERFORMAT_OP_NO && eOp2 != NUMBERFORMAT_OP_NO &&
5239 NumFor[2].GetCount() == 0 )
5241 bDefault[0] = bDefault[1] = bDefault[2] = bDefault[3] = true; // [];[];;
5243 // nothing to do if conditions specified for every subformat
5245 else if ( bDefault[0] )
5247 bDefault[0] = false; // a single unconditional subformat is never delimited
5249 else
5251 if ( bDefault[2] && NumFor[2].GetCount() == 0 && NumFor[1].GetCount() > 0 )
5253 bDefault[3] = true; // special cases x;x;; and ;x;;
5255 for ( int i=0; i<3 && !bDefault[i]; ++i )
5257 bDefault[i] = true;
5260 int nSem = 0; // needed ';' delimiters
5261 int nSub = 0; // subformats delimited so far
5262 for ( int n=0; n<4; n++ )
5264 if ( n > 0 && NumFor[n].Info().eScannedType != SvNumFormatType::UNDEFINED )
5266 nSem++;
5268 OUString aPrefix;
5270 if ( !bDefaults )
5272 switch ( n )
5274 case 0 :
5275 lcl_SvNumberformat_AddLimitStringImpl( aPrefix, eOp1,
5276 fLimit1, rLocWrp.getNumDecimalSep() );
5277 break;
5278 case 1 :
5279 lcl_SvNumberformat_AddLimitStringImpl( aPrefix, eOp2,
5280 fLimit2, rLocWrp.getNumDecimalSep() );
5281 break;
5285 const OUString& rColorName = NumFor[n].GetColorName();
5286 if ( !rColorName.isEmpty() )
5288 const NfKeywordTable & rKey = rScan.GetKeywords();
5289 for ( int j = NF_KEY_FIRSTCOLOR; j <= NF_KEY_LASTCOLOR; j++ )
5291 if ( rKey[j] == rColorName )
5293 aPrefix += "[" + rKeywords[j] + "]";
5294 break; // for
5299 SvNumberNatNum aNatNum = NumFor[n].GetNatNum();
5300 bool bDBNumInserted = false;
5301 if (aNatNum.IsComplete() && (aNatNum.GetDBNum() > 0 || nOriginalLang != LANGUAGE_DONTKNOW))
5302 { // GetFormatStringForExcel() may have changed language to en_US
5303 if (aNatNum.GetLang() == LANGUAGE_ENGLISH_US && nOriginalLang != LANGUAGE_DONTKNOW)
5304 aNatNum.SetLang( nOriginalLang );
5305 if ( aNatNum.GetDBNum() > 0 )
5307 aPrefix += "[DBNum" + OUString::number( aNatNum.GetDBNum() ) + "]";
5308 bDBNumInserted = true;
5312 sal_uInt16 nCnt = NumFor[n].GetCount();
5313 if ( nSem && (nCnt || !aPrefix.isEmpty()) )
5315 for ( ; nSem; --nSem )
5317 aStr.append( ';' );
5319 for ( ; nSub <= n; ++nSub )
5321 bDefault[nSub] = false;
5325 if ( !aPrefix.isEmpty() )
5327 aStr.append( aPrefix );
5329 sal_Int32 nPosHaveLCID = -1;
5330 sal_Int32 nPosInsertLCID = aStr.getLength();
5331 sal_uInt32 nCalendarID = 0x0000000; // Excel ID of calendar used in sub-format see tdf#36038
5332 constexpr sal_uInt32 kCalGengou = 0x0030000;
5333 if ( nCnt )
5335 auto& rTypeArray = NumFor[n].Info().nTypeArray;
5336 auto& rStrArray = NumFor[n].Info().sStrArray;
5337 for ( sal_uInt16 j=0; j<nCnt; j++ )
5339 if ( 0 <= rTypeArray[j] && rTypeArray[j] < NF_KEYWORD_ENTRIES_COUNT )
5341 aStr.append( rKeywords[rTypeArray[j]] );
5342 if( NF_KEY_NNNN == rTypeArray[j] )
5344 aStr.append( rLocWrp.getLongDateDayOfWeekSep() );
5346 switch (rTypeArray[j])
5348 case NF_KEY_EC:
5349 case NF_KEY_EEC:
5350 case NF_KEY_R:
5351 case NF_KEY_RR:
5352 // Implicit secondary (non-gregorian) calendar.
5353 // Currently only for ja-JP.
5354 /* TODO: same for all locales in
5355 * LocaleDataWrapper::doesSecondaryCalendarUseEC() ?
5356 * Should split the locales off that then. */
5357 if (!nCalendarID)
5359 const LanguageType nLang = MsLangId::getRealLanguage( nOriginalLang);
5360 if (nLang == LANGUAGE_JAPANESE)
5361 nCalendarID = kCalGengou;
5363 break;
5364 default:
5365 ; // nothing
5368 else
5370 switch ( rTypeArray[j] )
5372 case NF_SYMBOLTYPE_DECSEP :
5373 aStr.append( rLocWrp.getNumDecimalSep() );
5374 break;
5375 case NF_SYMBOLTYPE_THSEP :
5376 aStr.append( rLocWrp.getNumThousandSep() );
5377 break;
5378 case NF_SYMBOLTYPE_EXP :
5379 aStr.append( rKeywords[NF_KEY_E] );
5380 if ( rStrArray[j].getLength() > 1 && rStrArray[j][1] == '+' )
5381 aStr.append( "+" );
5382 else
5383 // tdf#102370: Excel code for exponent without sign
5384 aStr.append( "-" );
5385 break;
5386 case NF_SYMBOLTYPE_DATESEP :
5387 aStr.append( rLocWrp.getDateSep() );
5388 break;
5389 case NF_SYMBOLTYPE_TIMESEP :
5390 aStr.append( rLocWrp.getTimeSep() );
5391 break;
5392 case NF_SYMBOLTYPE_TIME100SECSEP :
5393 aStr.append( rLocWrp.getTime100SecSep() );
5394 break;
5395 case NF_SYMBOLTYPE_FRACBLANK :
5396 case NF_SYMBOLTYPE_STRING :
5397 if ( rStrArray[j].getLength() == 1 )
5399 if ( rTypeArray[j] == NF_SYMBOLTYPE_STRING )
5400 aStr.append( '\\' );
5401 aStr.append( rStrArray[j] );
5403 else
5405 aStr.append( '"' );
5406 aStr.append( rStrArray[j] );
5407 aStr.append( '"' );
5409 break;
5410 case NF_SYMBOLTYPE_CALDEL :
5411 if (j + 1 >= nCnt)
5412 break;
5413 if ( rStrArray[j+1] == "gengou" )
5415 nCalendarID = kCalGengou;
5417 else if ( rStrArray[j+1] == "hijri" )
5419 nCalendarID = 0x0060000;
5421 else if ( rStrArray[j+1] == "buddhist" )
5423 nCalendarID = 0x0070000;
5425 else if ( rStrArray[j+1] == "jewish" )
5427 nCalendarID = 0x0080000;
5429 // Other calendars (see tdf#36038) not corresponding between LibO and XL.
5430 // However, skip any calendar modifier and don't write
5431 // as format code (if not as literal string).
5432 j += 2;
5433 break;
5434 case NF_SYMBOLTYPE_CURREXT :
5435 nPosHaveLCID = aStr.getLength();
5436 aStr.append( rStrArray[j] );
5437 break;
5438 default:
5439 aStr.append( rStrArray[j] );
5444 sal_uInt32 nAlphabetID = 0x0000000; // Excel ID of alphabet used for numerals see tdf#36038
5445 LanguageType nLanguageID = LANGUAGE_SYSTEM;
5446 if ( aNatNum.IsComplete() )
5448 nLanguageID = MsLangId::getRealLanguage( aNatNum.GetLang());
5449 if ( aNatNum.GetNatNum() == 0 )
5451 nAlphabetID = 0x01000000; // Arabic-european numerals
5453 else if ( nCalendarID > 0 || aNatNum.GetDBNum() == 0 || aNatNum.GetDBNum() == aNatNum.GetNatNum() )
5454 { // if no DBNum code then use long LCID
5455 // if DBNum value != NatNum value, use DBNum and not extended LCID
5456 // if calendar, then DBNum will be removed
5457 LanguageType pri = primary(nLanguageID);
5458 if ( pri == LANGUAGE_ARABIC_PRIMARY_ONLY )
5459 nAlphabetID = 0x02000000; // Arabic-indic numerals
5460 else if ( pri == primary(LANGUAGE_FARSI) )
5461 nAlphabetID = 0x03000000; // Farsi numerals
5462 else if ( pri.anyOf(
5463 primary(LANGUAGE_HINDI),
5464 primary(LANGUAGE_MARATHI),
5465 primary(LANGUAGE_NEPALI) ))
5466 nAlphabetID = 0x04000000; // Devanagari numerals
5467 else if ( pri == primary(LANGUAGE_BENGALI) )
5468 nAlphabetID = 0x05000000; // Bengali numerals
5469 else if ( pri == primary(LANGUAGE_PUNJABI) )
5471 if ( nLanguageID == LANGUAGE_PUNJABI_ARABIC_LSO )
5472 nAlphabetID = 0x02000000; // Arabic-indic numerals
5473 else
5474 nAlphabetID = 0x06000000; // Punjabi numerals
5476 else if ( pri == primary(LANGUAGE_GUJARATI) )
5477 nAlphabetID = 0x07000000; // Gujarati numerals
5478 else if ( pri == primary(LANGUAGE_ODIA))
5479 nAlphabetID = 0x08000000; // Odia (Oriya) numerals
5480 else if ( pri == primary(LANGUAGE_TAMIL))
5481 nAlphabetID = 0x09000000; // Tamil numerals
5482 else if ( pri == primary(LANGUAGE_TELUGU))
5483 nAlphabetID = 0x0A000000; // Telugu numerals
5484 else if ( pri == primary(LANGUAGE_KANNADA))
5485 nAlphabetID = 0x0B000000; // Kannada numerals
5486 else if ( pri == primary(LANGUAGE_MALAYALAM))
5487 nAlphabetID = 0x0C000000; // Malayalam numerals
5488 else if ( pri == primary(LANGUAGE_THAI))
5490 // The Thai T NatNum modifier during Xcl export.
5491 if ( rKeywords[NF_KEY_THAI_T] == "T" )
5492 nAlphabetID = 0x0D000000; // Thai numerals
5494 else if ( pri == primary(LANGUAGE_LAO))
5495 nAlphabetID = 0x0E000000; // Lao numerals
5496 else if ( pri == primary(LANGUAGE_TIBETAN))
5497 nAlphabetID = 0x0F000000; // Tibetan numerals
5498 else if ( pri == primary(LANGUAGE_BURMESE))
5499 nAlphabetID = 0x10000000; // Burmese numerals
5500 else if ( pri == primary(LANGUAGE_TIGRIGNA_ETHIOPIA))
5501 nAlphabetID = 0x11000000; // Tigrigna numerals
5502 else if ( pri == primary(LANGUAGE_KHMER))
5503 nAlphabetID = 0x12000000; // Khmer numerals
5504 else if ( pri == primary(LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA))
5506 if ( nLanguageID != LANGUAGE_MONGOLIAN_CYRILLIC_MONGOLIA
5507 && nLanguageID != LANGUAGE_MONGOLIAN_CYRILLIC_LSO )
5508 nAlphabetID = 0x13000000; // Mongolian numerals
5510 // CJK numerals
5511 else if ( pri == primary(LANGUAGE_JAPANESE))
5513 nAlphabetID = 0x1B;
5514 lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() );
5516 else if ( pri == primary(LANGUAGE_CHINESE))
5518 if ( nLanguageID == LANGUAGE_CHINESE_TRADITIONAL
5519 || nLanguageID == LANGUAGE_CHINESE_HONGKONG
5520 || nLanguageID == LANGUAGE_CHINESE_MACAU )
5522 nAlphabetID = 0x21;
5523 lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() );
5525 else // LANGUAGE_CHINESE_SIMPLIFIED
5527 nAlphabetID = 0x1E;
5528 lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() );
5531 else if ( pri == primary(LANGUAGE_KOREAN))
5533 if ( aNatNum.GetNatNum() == 9 ) // Hangul
5535 nAlphabetID = 0x27000000;
5537 else
5539 nAlphabetID = 0x24;
5540 lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() );
5544 // Add LCID to DBNum
5545 if ( aNatNum.GetDBNum() > 0 && nLanguageID == LANGUAGE_SYSTEM )
5546 nLanguageID = MsLangId::getRealLanguage( aNatNum.GetLang());
5548 else if (nPosHaveLCID < 0)
5550 // Do not insert a duplicated LCID that was already given with a
5551 // currency format as [$R-1C09]
5552 if (!bSystemLanguage && nOriginalLang != LANGUAGE_DONTKNOW)
5554 // Explicit locale, write only to the first subformat.
5555 if (n == 0)
5556 nLanguageID = MsLangId::getRealLanguage( nOriginalLang);
5558 else if (bSystemLanguage && maLocale.meLanguageWithoutLocaleData != LANGUAGE_DONTKNOW)
5560 // Explicit locale but no locale data thus assigned to system
5561 // locale, preserve for roundtrip, write only to the first
5562 // subformat.
5563 if (n == 0)
5564 nLanguageID = maLocale.meLanguageWithoutLocaleData;
5567 if ( nCalendarID > 0 )
5568 { // Add alphabet and language to calendar
5569 if ( nAlphabetID == 0 )
5570 nAlphabetID = 0x01000000;
5571 if ( nLanguageID == LANGUAGE_SYSTEM && nOriginalLang != LANGUAGE_DONTKNOW )
5572 nLanguageID = nOriginalLang;
5574 lcl_insertLCID( aStr, nAlphabetID + nCalendarID + static_cast<sal_uInt16>(nLanguageID), nPosInsertLCID,
5575 bDBNumInserted);
5577 for ( ; nSub<4 && bDefault[nSub]; ++nSub )
5578 { // append empty subformats
5579 aStr.append( ';' );
5581 return aStr.makeStringAndClear();
5584 OUString SvNumberformat::ImpGetNatNumString( const SvNumberNatNum& rNum,
5585 sal_Int64 nVal, sal_uInt16 nMinDigits ) const
5587 OUString aStr;
5588 if ( nMinDigits )
5590 if ( nMinDigits == 2 )
5592 // speed up the most common case
5593 if ( 0 <= nVal && nVal < 10 )
5595 sal_Unicode aBuf[2];
5596 aBuf[0] = '0';
5597 aBuf[1] = '0' + nVal;
5598 aStr = OUString(aBuf, SAL_N_ELEMENTS(aBuf));
5600 else
5602 aStr = OUString::number( nVal );
5605 else
5607 OUString aValStr( OUString::number( nVal ) );
5608 if ( aValStr.getLength() >= nMinDigits )
5610 aStr = aValStr;
5612 else
5614 OUStringBuffer aBuf;
5615 for(sal_Int32 index = 0; index < nMinDigits - aValStr.getLength(); ++index)
5617 aBuf.append('0');
5619 aBuf.append(aValStr);
5620 aStr = aBuf.makeStringAndClear();
5624 else
5626 aStr = OUString::number( nVal );
5628 return impTransliterate(aStr, rNum);
5631 OUString SvNumberformat::impTransliterateImpl(const OUString& rStr,
5632 const SvNumberNatNum& rNum ) const
5634 css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() );
5635 return GetFormatter().GetNatNum()->getNativeNumberStringParams(rStr, aLocale, rNum.GetNatNum(),
5636 rNum.GetParams());
5639 void SvNumberformat::impTransliterateImpl(OUStringBuffer& rStr,
5640 const SvNumberNatNum& rNum ) const
5642 css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() );
5644 OUString sTemp(rStr.toString());
5645 sTemp = GetFormatter().GetNatNum()->getNativeNumberStringParams(
5646 sTemp, aLocale, rNum.GetNatNum(), rNum.GetParams());
5647 rStr = sTemp;
5650 OUString SvNumberformat::impTransliterateImpl(const OUString& rStr,
5651 const SvNumberNatNum& rNum,
5652 const sal_uInt16 nDateKey) const
5654 // no KEYWORD=argument list in NatNum12
5655 if (rNum.GetParams().indexOf('=') == -1)
5656 return impTransliterateImpl( rStr, rNum);
5658 const NfKeywordTable & rKeywords = rScan.GetKeywords();
5660 // Format: KEYWORD=numbertext_prefix, ..., for example:
5661 // [NatNum12 YYYY=title ordinal,MMMM=article, D=ordinal-number]
5662 sal_Int32 nField = -1;
5665 nField = rNum.GetParams().indexOf(OUStringConcatenation(rKeywords[nDateKey] + "="), ++nField);
5667 while (nField != -1 && nField != 0 &&
5668 (rNum.GetParams()[nField - 1] != ',' &&
5669 rNum.GetParams()[nField - 1] != ' '));
5671 // no format specified for actual keyword
5672 if (nField == -1)
5673 return rStr;
5675 sal_Int32 nKeywordLen = rKeywords[nDateKey].getLength() + 1;
5676 sal_Int32 nFieldEnd = rNum.GetParams().indexOf(',', nField);
5678 if (nFieldEnd == -1)
5679 nFieldEnd = rNum.GetParams().getLength();
5681 css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() );
5683 return GetFormatter().GetNatNum()->getNativeNumberStringParams(
5684 rStr, aLocale, rNum.GetNatNum(),
5685 rNum.GetParams().copy(nField + nKeywordLen, nFieldEnd - nField - nKeywordLen));
5688 void SvNumberformat::GetNatNumXml( css::i18n::NativeNumberXmlAttributes2& rAttr,
5689 sal_uInt16 nNumFor ) const
5691 if ( nNumFor <= 3 )
5693 const SvNumberNatNum& rNum = NumFor[nNumFor].GetNatNum();
5694 if ( rNum.IsSet() )
5696 css::lang::Locale aLocale(
5697 LanguageTag( rNum.GetLang() ).getLocale() );
5699 /* TODO: a new XNativeNumberSupplier2::convertToXmlAttributes()
5700 * should rather return NativeNumberXmlAttributes2 and places
5701 * adapted, and whether to fill Spellout or something different
5702 * should be internal there. */
5703 css::i18n::NativeNumberXmlAttributes aTmp(
5704 GetFormatter().GetNatNum()->convertToXmlAttributes(
5705 aLocale, rNum.GetNatNum()));
5706 rAttr.Locale = aTmp.Locale;
5707 rAttr.Format = aTmp.Format;
5708 rAttr.Style = aTmp.Style;
5709 if ( NatNumTakesParameters(rNum.GetNatNum()) )
5711 // NatNum12 spell out numbers, dates and money amounts
5712 rAttr.Spellout = rNum.GetParams();
5713 // Mutually exclusive.
5714 rAttr.Format.clear();
5715 rAttr.Style.clear();
5717 else
5719 rAttr.Spellout.clear();
5722 else
5724 rAttr = css::i18n::NativeNumberXmlAttributes2();
5727 else
5729 rAttr = css::i18n::NativeNumberXmlAttributes2();
5733 // static
5734 bool SvNumberformat::HasStringNegativeSign( const OUString& rStr )
5736 // For Sign '-' needs to be at the start or at the end of the string (blanks ignored)
5737 sal_Int32 nLen = rStr.getLength();
5738 if ( !nLen )
5740 return false;
5742 const sal_Unicode* const pBeg = rStr.getStr();
5743 const sal_Unicode* const pEnd = pBeg + nLen;
5744 const sal_Unicode* p = pBeg;
5746 { // Start
5747 if ( *p == '-' )
5749 return true;
5752 while ( *p == ' ' && ++p < pEnd );
5754 p = pEnd - 1;
5757 { // End
5758 if ( *p == '-' )
5760 return true;
5763 while ( *p == ' ' && pBeg < --p );
5764 return false;
5767 // static
5768 bool SvNumberformat::IsInQuote( const OUString& rStr, sal_Int32 nPos,
5769 sal_Unicode cQuote, sal_Unicode cEscIn, sal_Unicode cEscOut )
5771 sal_Int32 nLen = rStr.getLength();
5772 if ( nPos >= nLen )
5774 return false;
5776 const sal_Unicode* p0 = rStr.getStr();
5777 const sal_Unicode* p = p0;
5778 const sal_Unicode* p1 = p0 + nPos;
5779 bool bQuoted = false;
5780 while ( p <= p1 )
5782 if ( *p == cQuote )
5784 if ( p == p0 )
5786 bQuoted = true;
5788 else if ( bQuoted )
5790 if ( *(p-1) != cEscIn )
5792 bQuoted = false;
5795 else
5797 if ( *(p-1) != cEscOut )
5799 bQuoted = true;
5803 p++;
5805 return bQuoted;
5808 // static
5809 sal_Int32 SvNumberformat::GetQuoteEnd( const OUString& rStr, sal_Int32 nPos,
5810 sal_Unicode cQuote, sal_Unicode cEscIn )
5812 if ( nPos < 0 )
5814 return -1;
5816 sal_Int32 nLen = rStr.getLength();
5817 if ( nPos >= nLen )
5819 return -1;
5821 if ( !IsInQuote( rStr, nPos, cQuote, cEscIn ) )
5823 if ( rStr[ nPos ] == cQuote )
5825 return nPos; // Closing cQuote
5827 return -1;
5829 const sal_Unicode* p0 = rStr.getStr();
5830 const sal_Unicode* p = p0 + nPos;
5831 const sal_Unicode* p1 = p0 + nLen;
5832 while ( p < p1 )
5834 if ( *p == cQuote && p > p0 && *(p-1) != cEscIn )
5836 return sal::static_int_cast< sal_Int32 >(p - p0);
5838 p++;
5840 return nLen; // End of String
5843 sal_uInt16 SvNumberformat::GetNumForNumberElementCount( sal_uInt16 nNumFor ) const
5845 if ( nNumFor < 4 )
5847 sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
5848 return nCnt - ImpGetNumForStringElementCount( nNumFor );
5850 return 0;
5853 sal_uInt16 SvNumberformat::ImpGetNumForStringElementCount( sal_uInt16 nNumFor ) const
5855 sal_uInt16 nCnt = 0;
5856 sal_uInt16 nNumForCnt = NumFor[nNumFor].GetCount();
5857 auto& rTypeArray = NumFor[nNumFor].Info().nTypeArray;
5858 for ( sal_uInt16 j=0; j<nNumForCnt; ++j )
5860 switch ( rTypeArray[j] )
5862 case NF_SYMBOLTYPE_STRING:
5863 case NF_SYMBOLTYPE_CURRENCY:
5864 case NF_SYMBOLTYPE_DATESEP:
5865 case NF_SYMBOLTYPE_TIMESEP:
5866 case NF_SYMBOLTYPE_TIME100SECSEP:
5867 case NF_SYMBOLTYPE_PERCENT:
5868 ++nCnt;
5869 break;
5872 return nCnt;
5875 bool SvNumberformat::IsMinuteSecondFormat() const
5877 if (GetMaskedType() != SvNumFormatType::TIME)
5878 return false;
5880 constexpr sal_uInt16 k00 = 0x00; // Nada, Nilch
5881 constexpr sal_uInt16 kLB = 0x01; // '[' Left Bracket
5882 constexpr sal_uInt16 kRB = 0x02; // ']' Right Bracket
5883 constexpr sal_uInt16 kMM = 0x04; // M or MM
5884 constexpr sal_uInt16 kTS = 0x08; // Time Separator
5885 constexpr sal_uInt16 kSS = 0x10; // S or SS
5886 #define HAS_MINUTE_SECOND(state) ((state) == (kMM|kTS|kSS) || (state) == (kLB|kMM|kRB|kTS|kSS))
5887 // Also (kMM|kTS|kLB|kSS|kRB) but those are the same bits.
5889 sal_uInt16 nState = k00;
5890 bool bSep = false;
5891 sal_uInt16 nNumForCnt = NumFor[0].GetCount();
5892 auto const & rTypeArray = NumFor[0].Info().nTypeArray;
5893 for (sal_uInt16 j=0; j < nNumForCnt; ++j)
5895 switch (rTypeArray[j])
5897 case NF_SYMBOLTYPE_DEL:
5899 // '[' or ']' before/after MM or SS
5900 const OUString& rStr = NumFor[0].Info().sStrArray[j];
5901 if (rStr == "[")
5903 if (nState != k00 && nState != (kMM|kTS))
5904 return false;
5905 nState |= kLB;
5907 else if (rStr == "]")
5909 if (nState != (kLB|kMM) && nState != (kMM|kTS|kLB|kSS))
5910 return false;
5911 nState |= kRB;
5913 else
5914 return false;
5916 break;
5917 case NF_KEY_MI:
5918 case NF_KEY_MMI:
5919 if (nState != k00 && nState != kLB)
5920 return false;
5921 nState |= kMM;
5922 break;
5923 case NF_SYMBOLTYPE_TIMESEP:
5924 if (nState != kMM && nState != (kLB|kMM|kRB))
5925 return false;
5926 nState |= kTS;
5927 break;
5928 case NF_KEY_S:
5929 case NF_KEY_SS:
5930 if (nState != (kMM|kTS) && nState != (kLB|kMM|kRB|kTS) && nState != (kMM|kTS|kLB))
5931 return false;
5932 nState |= kSS;
5933 break;
5934 case NF_SYMBOLTYPE_TIME100SECSEP:
5935 // Trailing fraction of seconds allowed.
5936 if (!HAS_MINUTE_SECOND(nState))
5937 return false;
5938 bSep = true;
5939 break;
5940 case NF_SYMBOLTYPE_DIGIT:
5941 if (!bSep)
5942 return false;
5943 break;
5944 case NF_SYMBOLTYPE_STRING:
5945 // nothing, display literal
5946 break;
5947 default:
5948 return false;
5951 return HAS_MINUTE_SECOND(nState);
5952 #undef HAS_MINUTE_SECOND
5955 OUString SvNumberformat::GetFormatStringForTimePrecision( int nPrecision ) const
5957 OUStringBuffer sString;
5958 using comphelper::string::padToLength;
5960 sal_uInt16 nNumForCnt = NumFor[0].GetCount();
5961 auto const & rTypeArray = NumFor[0].Info().nTypeArray;
5962 for (sal_uInt16 j=0; j < nNumForCnt; ++j)
5964 switch (rTypeArray[j])
5966 case NF_KEY_S :
5967 case NF_KEY_SS:
5968 sString.append( NumFor[0].Info().sStrArray[j] );
5969 if ( j > 0 && rTypeArray[j-1] == NF_SYMBOLTYPE_DEL && j < nNumForCnt-1 )
5971 j++;
5972 sString.append( NumFor[0].Info().sStrArray[j] );
5974 if (nPrecision > 0)
5976 sString.append( rLoc().getTime100SecSep() );
5977 padToLength(sString, sString.getLength() + nPrecision, '0');
5979 break;
5980 case NF_SYMBOLTYPE_TIME100SECSEP:
5981 case NF_SYMBOLTYPE_DIGIT:
5982 break;
5983 case NF_SYMBOLTYPE_STRING:
5984 sString.append( "\"" );
5985 [[fallthrough]];
5986 default:
5987 sString.append( NumFor[0].Info().sStrArray[j] );
5988 if (rTypeArray[j] == NF_SYMBOLTYPE_STRING)
5990 sString.append( "\"" );
5995 return sString.makeStringAndClear();
5998 sal_uInt16 SvNumberformat::GetThousandDivisorPrecision( sal_uInt16 nIx ) const
6000 if (nIx >= 4)
6001 return 0;
6003 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
6005 if (rInfo.eScannedType != SvNumFormatType::NUMBER && rInfo.eScannedType != SvNumFormatType::CURRENCY)
6006 return 0;
6008 if (rInfo.nThousand == FLAG_STANDARD_IN_FORMAT)
6009 return SvNumberFormatter::UNLIMITED_PRECISION;
6011 return rInfo.nThousand * 3;
6014 const CharClass& SvNumberformat::rChrCls() const
6016 return rScan.GetChrCls();
6019 const LocaleDataWrapper& SvNumberformat::rLoc() const
6021 return rScan.GetLoc();
6024 CalendarWrapper& SvNumberformat::GetCal() const
6026 return rScan.GetCal();
6029 const SvNumberFormatter& SvNumberformat::GetFormatter() const
6031 return *rScan.GetNumberformatter();
6034 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */