tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / svl / source / numbers / zformat.cxx
blobeda77fc62859a1b22ff177ad8bee4382fa7c0e20
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 <string_view>
22 #include <o3tl/sprintf.hxx>
23 #include <o3tl/string_view.hxx>
24 #include <comphelper/string.hxx>
25 #include <sal/log.hxx>
26 #include <tools/debug.hxx>
27 #include <tools/long.hxx>
28 #include <i18nlangtag/mslangid.hxx>
29 #include <rtl/math.hxx>
30 #include <unotools/charclass.hxx>
31 #include <unotools/calendarwrapper.hxx>
32 #include <unotools/nativenumberwrapper.hxx>
33 #include <com/sun/star/i18n/CalendarFieldIndex.hpp>
34 #include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
35 #include <com/sun/star/i18n/CalendarDisplayCode.hpp>
36 #include <com/sun/star/i18n/AmPmValue.hpp>
37 #include <com/sun/star/i18n/NativeNumberMode.hpp>
38 #include <com/sun/star/i18n/NativeNumberXmlAttributes2.hpp>
40 #include <svl/zformat.hxx>
41 #include "zforscan.hxx"
43 #include "zforfind.hxx"
44 #include <svl/zforlist.hxx>
45 #include <unotools/digitgroupingiterator.hxx>
46 #include <svl/nfsymbol.hxx>
48 #include <cmath>
49 #include <array>
51 using namespace svt;
53 namespace {
55 constexpr OUString GREGORIAN = u"gregorian"_ustr;
57 const sal_uInt16 UPPER_PRECISION = 300; // entirely arbitrary...
58 const double EXP_LOWER_BOUND = 1.0E-4; // prefer scientific notation below this value.
59 const double EXP_ABS_UPPER_BOUND = 1.0E15; // use exponential notation above that absolute value.
60 // Back in time was E16 that lead
61 // to display rounding errors, see
62 // also sal/rtl/math.cxx
63 // doubleToString()
65 constexpr sal_Int32 kTimeSignificantRound = 7; // Round (date+)time at 7 decimals
66 // (+5 of 86400 == 12 significant digits).
68 const sal_Unicode cBlankDigit = 0x2007; // tdf#158890 use figure space for '?'
69 } // namespace
71 const double D_MAX_U_INT32 = double(0xffffffff); // 4294967295.0
72 constexpr double D_MAX_INTEGER = (sal_uInt64(1) << 53) - 1;
74 const double D_MAX_D_BY_100 = 1.7E306;
75 const double D_MIN_M_BY_1000 = 2.3E-305;
77 const sal_uInt8 cCharWidths[ 128-32 ] = {
78 1,1,1,2,2,3,2,1,1,1,1,2,1,1,1,1,
79 2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,2,
80 3,2,2,2,2,2,2,3,2,1,2,2,2,3,3,3,
81 2,3,2,2,2,2,2,3,2,2,2,1,1,1,2,2,
82 1,2,2,2,2,2,1,2,2,1,1,2,1,3,2,2,
83 2,2,1,2,1,2,2,2,2,2,2,1,1,1,2,1
86 // static
87 sal_Int32 SvNumberformat::InsertBlanks( OUStringBuffer& r, sal_Int32 nPos, sal_Unicode c )
89 if( c >= 32 )
91 int n = 2; // Default for chars > 128 (HACK!)
92 if( c <= 127 )
94 n = static_cast<int>(cCharWidths[ c - 32 ]);
96 while( n-- )
98 r.insert( nPos++, ' ');
101 return nPos;
104 static tools::Long GetPrecExp( double fAbsVal )
106 DBG_ASSERT( fAbsVal > 0.0, "GetPrecExp: fAbsVal <= 0.0" );
107 if ( fAbsVal < 1e-7 || fAbsVal > 1e7 )
109 // Shear: whether it's faster or not, falls in between 1e6 and 1e7
110 return static_cast<tools::Long>(floor( log10( fAbsVal ) )) + 1;
112 else
114 tools::Long nPrecExp = 1;
115 while( fAbsVal < 1 )
117 fAbsVal *= 10;
118 nPrecExp--;
120 while( fAbsVal >= 10 )
122 fAbsVal /= 10;
123 nPrecExp++;
125 return nPrecExp;
130 * SvNumberformatInfo
131 * */
133 void ImpSvNumberformatInfo::Copy( const ImpSvNumberformatInfo& rNumFor, sal_uInt16 nCnt )
135 for (sal_uInt16 i = 0; i < nCnt; ++i)
137 sStrArray[i] = rNumFor.sStrArray[i];
138 nTypeArray[i] = rNumFor.nTypeArray[i];
140 eScannedType = rNumFor.eScannedType;
141 bThousand = rNumFor.bThousand;
142 nThousand = rNumFor.nThousand;
143 nCntPre = rNumFor.nCntPre;
144 nCntPost = rNumFor.nCntPost;
145 nCntExp = rNumFor.nCntExp;
148 const std::map<LanguageType, std::array<sal_uInt8, 4>> tblDBNumToNatNum
149 = { { primary(LANGUAGE_CHINESE), { 4, 5, 3, 0 } },
150 { primary(LANGUAGE_JAPANESE), { 4, 5, 3, 0 } },
151 { primary(LANGUAGE_KOREAN), { 4, 5, 6, 10 } } };
153 // static
154 sal_uInt8 SvNumberNatNum::MapDBNumToNatNum( sal_uInt8 nDBNum, LanguageType eLang, bool bDate )
156 sal_uInt8 nNatNum = 0;
157 eLang = MsLangId::getRealLanguage( eLang ); // resolve SYSTEM etc.
158 eLang = primary(eLang); // 10 bit primary language
159 if ( bDate )
161 if ( nDBNum == 4 && eLang == primary(LANGUAGE_KOREAN) )
163 nNatNum = 10;
165 else if ( nDBNum <= 3 )
167 nNatNum = nDBNum; // known to be good for: zh,ja,ko / 1,2,3
170 else
172 if (1 <= nDBNum && nDBNum <= 4)
174 auto const it = tblDBNumToNatNum.find(eLang);
175 if (it != tblDBNumToNatNum.end())
176 nNatNum = it->second[nDBNum - 1];
180 return nNatNum;
183 const std::map<LanguageType, std::array<sal_uInt8, 9>> tblNatNumToDBNum
184 = { { primary(LANGUAGE_CHINESE), { 1, 0, 0, 1, 2, 3, 0, 0, 0 } },
185 { primary(LANGUAGE_JAPANESE), { 1, 2, 3, 1, 2, 3, 1, 2, 0 } },
186 { primary(LANGUAGE_KOREAN), { 1, 2, 3, 1, 2, 3, 1, 2, 4 } } };
188 // static
189 sal_uInt8 SvNumberNatNum::MapNatNumToDBNum( sal_uInt8 nNatNum, LanguageType eLang, bool bDate )
191 sal_uInt8 nDBNum = 0;
192 eLang = MsLangId::getRealLanguage( eLang ); // resolve SYSTEM etc.
193 eLang = primary(eLang); // 10 bit primary language
194 if ( bDate )
196 if ( nNatNum == 10 && eLang == primary(LANGUAGE_KOREAN) )
198 nDBNum = 4;
200 else if ( nNatNum <= 3 )
202 nDBNum = nNatNum; // known to be good for: zh,ja,ko / 1,2,3
205 else
207 if (1 <= nNatNum && nNatNum <= 9)
209 auto const it = tblNatNumToDBNum.find(eLang);
210 if (it != tblNatNumToDBNum.end())
211 nDBNum = it->second[nNatNum - 1];
214 return nDBNum;
218 * SvNumFor
221 ImpSvNumFor::ImpSvNumFor()
223 nStringsCnt = 0;
224 aI.eScannedType = SvNumFormatType::UNDEFINED;
225 aI.bThousand = false;
226 aI.nThousand = 0;
227 aI.nCntPre = 0;
228 aI.nCntPost = 0;
229 aI.nCntExp = 0;
230 pColor = nullptr;
233 ImpSvNumFor::~ImpSvNumFor()
237 void ImpSvNumFor::Enlarge(sal_uInt16 nCnt)
239 if ( nStringsCnt != nCnt )
241 nStringsCnt = nCnt;
242 aI.nTypeArray.resize(nCnt);
243 aI.sStrArray.resize(nCnt);
247 void ImpSvNumFor::Copy( const ImpSvNumFor& rNumFor, const ImpSvNumberformatScan* pSc )
249 Enlarge( rNumFor.nStringsCnt );
250 aI.Copy( rNumFor.aI, nStringsCnt );
251 sColorName = rNumFor.sColorName;
252 if ( pSc )
254 pColor = pSc->GetColor( sColorName ); // #121103# don't copy pointer between documents
256 else
258 pColor = rNumFor.pColor;
260 aNatNum = rNumFor.aNatNum;
263 bool ImpSvNumFor::HasNewCurrency() const
265 for ( sal_uInt16 j=0; j<nStringsCnt; j++ )
267 if ( aI.nTypeArray[j] == NF_SYMBOLTYPE_CURRENCY )
269 return true;
272 return false;
275 bool ImpSvNumFor::GetNewCurrencySymbol( OUString& rSymbol,
276 OUString& rExtension ) const
278 for ( sal_uInt16 j=0; j<nStringsCnt; j++ )
280 if ( aI.nTypeArray[j] == NF_SYMBOLTYPE_CURRENCY )
282 rSymbol = aI.sStrArray[j];
283 if ( j < nStringsCnt-1 && aI.nTypeArray[j+1] == NF_SYMBOLTYPE_CURREXT )
285 rExtension = aI.sStrArray[j+1];
287 else
289 rExtension.clear();
291 return true;
294 //! No Erase at rSymbol, rExtension
295 return false;
299 * SvNumberformat
302 namespace {
304 enum BracketFormatSymbolType
306 BRACKET_SYMBOLTYPE_FORMAT = -1, // subformat string
307 BRACKET_SYMBOLTYPE_COLOR = -2, // color
308 BRACKET_SYMBOLTYPE_ERROR = -3, // error
309 BRACKET_SYMBOLTYPE_DBNUM1 = -4, // DoubleByteNumber, represent numbers
310 BRACKET_SYMBOLTYPE_DBNUM2 = -5, // using CJK characters, Excel compatible
311 BRACKET_SYMBOLTYPE_DBNUM3 = -6,
312 BRACKET_SYMBOLTYPE_DBNUM4 = -7,
313 BRACKET_SYMBOLTYPE_DBNUM5 = -8,
314 BRACKET_SYMBOLTYPE_DBNUM6 = -9,
315 BRACKET_SYMBOLTYPE_DBNUM7 = -10,
316 BRACKET_SYMBOLTYPE_DBNUM8 = -11,
317 BRACKET_SYMBOLTYPE_DBNUM9 = -12,
318 BRACKET_SYMBOLTYPE_LOCALE = -13,
319 BRACKET_SYMBOLTYPE_NATNUM0 = -14, // Our NativeNumber support, ASCII
320 BRACKET_SYMBOLTYPE_NATNUM1 = -15, // Our NativeNumber support, represent
321 BRACKET_SYMBOLTYPE_NATNUM2 = -16, // numbers using CJK, CTL, ...
322 BRACKET_SYMBOLTYPE_NATNUM3 = -17,
323 BRACKET_SYMBOLTYPE_NATNUM4 = -18,
324 BRACKET_SYMBOLTYPE_NATNUM5 = -19,
325 BRACKET_SYMBOLTYPE_NATNUM6 = -20,
326 BRACKET_SYMBOLTYPE_NATNUM7 = -21,
327 BRACKET_SYMBOLTYPE_NATNUM8 = -22,
328 BRACKET_SYMBOLTYPE_NATNUM9 = -23,
329 BRACKET_SYMBOLTYPE_NATNUM10 = -24,
330 BRACKET_SYMBOLTYPE_NATNUM11 = -25,
331 BRACKET_SYMBOLTYPE_NATNUM12 = -26,
332 BRACKET_SYMBOLTYPE_NATNUM13 = -27,
333 BRACKET_SYMBOLTYPE_NATNUM14 = -28,
334 BRACKET_SYMBOLTYPE_NATNUM15 = -29,
335 BRACKET_SYMBOLTYPE_NATNUM16 = -30,
336 BRACKET_SYMBOLTYPE_NATNUM17 = -31,
337 BRACKET_SYMBOLTYPE_NATNUM18 = -32,
338 BRACKET_SYMBOLTYPE_NATNUM19 = -33
343 void SvNumberformat::ImpCopyNumberformat( const SvNumberformat& rFormat )
345 sFormatstring = rFormat.sFormatstring;
346 eType = rFormat.eType;
347 maLocale = rFormat.maLocale;
348 fLimit1 = rFormat.fLimit1;
349 fLimit2 = rFormat.fLimit2;
350 eOp1 = rFormat.eOp1;
351 eOp2 = rFormat.eOp2;
352 bStandard = rFormat.bStandard;
353 bIsUsed = rFormat.bIsUsed;
354 sComment = rFormat.sComment;
355 bAdditionalBuiltin = rFormat.bAdditionalBuiltin;
357 // #121103# when copying between documents, get color pointers from own scanner
358 ImpSvNumberformatScan* pColorSc = ( &rScan != &rFormat.rScan ) ? &rScan : nullptr;
360 for (sal_uInt16 i = 0; i < 4; i++)
362 NumFor[i].Copy(rFormat.NumFor[i], pColorSc);
366 SvNumberformat::SvNumberformat( SvNumberformat const & rFormat )
367 : rScan(rFormat.rScan)
369 ImpCopyNumberformat( rFormat );
372 SvNumberformat::SvNumberformat( SvNumberformat const & rFormat, ImpSvNumberformatScan& rSc )
373 : rScan(rSc)
375 ImpCopyNumberformat( rFormat );
378 static bool lcl_SvNumberformat_IsBracketedPrefix( short nSymbolType )
380 if ( nSymbolType > 0 )
382 return true; // conditions
384 switch ( nSymbolType )
386 case BRACKET_SYMBOLTYPE_COLOR :
387 case BRACKET_SYMBOLTYPE_DBNUM1 :
388 case BRACKET_SYMBOLTYPE_DBNUM2 :
389 case BRACKET_SYMBOLTYPE_DBNUM3 :
390 case BRACKET_SYMBOLTYPE_DBNUM4 :
391 case BRACKET_SYMBOLTYPE_DBNUM5 :
392 case BRACKET_SYMBOLTYPE_DBNUM6 :
393 case BRACKET_SYMBOLTYPE_DBNUM7 :
394 case BRACKET_SYMBOLTYPE_DBNUM8 :
395 case BRACKET_SYMBOLTYPE_DBNUM9 :
396 case BRACKET_SYMBOLTYPE_LOCALE :
397 case BRACKET_SYMBOLTYPE_NATNUM0 :
398 case BRACKET_SYMBOLTYPE_NATNUM1 :
399 case BRACKET_SYMBOLTYPE_NATNUM2 :
400 case BRACKET_SYMBOLTYPE_NATNUM3 :
401 case BRACKET_SYMBOLTYPE_NATNUM4 :
402 case BRACKET_SYMBOLTYPE_NATNUM5 :
403 case BRACKET_SYMBOLTYPE_NATNUM6 :
404 case BRACKET_SYMBOLTYPE_NATNUM7 :
405 case BRACKET_SYMBOLTYPE_NATNUM8 :
406 case BRACKET_SYMBOLTYPE_NATNUM9 :
407 case BRACKET_SYMBOLTYPE_NATNUM10 :
408 case BRACKET_SYMBOLTYPE_NATNUM11 :
409 case BRACKET_SYMBOLTYPE_NATNUM12 :
410 case BRACKET_SYMBOLTYPE_NATNUM13 :
411 case BRACKET_SYMBOLTYPE_NATNUM14 :
412 case BRACKET_SYMBOLTYPE_NATNUM15 :
413 case BRACKET_SYMBOLTYPE_NATNUM16 :
414 case BRACKET_SYMBOLTYPE_NATNUM17 :
415 case BRACKET_SYMBOLTYPE_NATNUM18 :
416 case BRACKET_SYMBOLTYPE_NATNUM19 :
417 return true;
419 return false;
422 /** Import extended LCID from Excel
424 OUString SvNumberformat::ImpObtainCalendarAndNumerals( OUStringBuffer& rString, sal_Int32 nPos,
425 LanguageType& nLang, const LocaleType& aTmpLocale )
427 OUString sCalendar;
428 sal_uInt16 nNatNum = 0;
429 LanguageType nLocaleLang = MsLangId::getRealLanguage( maLocale.meLanguage );
430 LanguageType nTmpLocaleLang = MsLangId::getRealLanguage( aTmpLocale.meLanguage );
431 /* NOTE: enhancement to allow other possible locale dependent
432 * calendars and numerals. BUT only if our locale data allows it! For LCID
433 * numerals and calendars see
434 * http://office.microsoft.com/en-us/excel/HA010346351033.aspx
435 * Calendar is inserted after
436 * all prefixes have been consumed as it is actually a format modifier
437 * and not a prefix.
438 * Currently calendars are tied to the locale of the entire number
439 * format, e.g. [~buddhist] in en_US doesn't work.
440 * => Having different locales in sub formats does not work!
441 * */
442 /* TODO: calendars could be tied to a sub format's NatNum info
443 * instead, or even better be available for any locale. Needs a
444 * different implementation of GetCal() and locale data calendars.
445 * */
446 switch ( aTmpLocale.mnCalendarType & 0x7F )
448 case 0x03 : // Gengou calendar
449 // Only Japanese language support Gengou calendar.
450 // It is an implicit "other" calendar where E, EE, R and RR
451 // automatically switch to and YY and YYYY switch to Gregorian. Do
452 // not add the "[~gengou]" modifier.
453 if ( nLocaleLang != LANGUAGE_JAPANESE )
455 nLang = maLocale.meLanguage = LANGUAGE_JAPANESE;
457 break;
458 case 0x05 : // Korean Dangi calendar
459 sCalendar = "[~dangi]";
460 // Only Korean language support dangi calendar
461 if ( nLocaleLang != LANGUAGE_KOREAN )
463 nLang = maLocale.meLanguage = LANGUAGE_KOREAN;
465 break;
466 case 0x06 : // Hijri calendar
467 case 0x17 : // same?
468 sCalendar = "[~hijri]";
469 // Only Arabic or Farsi languages support Hijri calendar
470 if ( ( primary( nLocaleLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY )
471 && nLocaleLang != LANGUAGE_FARSI )
473 if ( ( primary( nTmpLocaleLang ) == LANGUAGE_ARABIC_PRIMARY_ONLY )
474 || nTmpLocaleLang == LANGUAGE_FARSI )
476 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
478 else
480 nLang = maLocale.meLanguage = LANGUAGE_ARABIC_SAUDI_ARABIA;
483 break;
484 case 0x07 : // Buddhist calendar
485 sCalendar="[~buddhist]";
486 // Only Thai or Lao languages support Buddhist calendar
487 if ( nLocaleLang != LANGUAGE_THAI && nLocaleLang != LANGUAGE_LAO )
489 if ( nTmpLocaleLang == LANGUAGE_THAI || nTmpLocaleLang == LANGUAGE_LAO )
491 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
493 else
495 nLang = maLocale.meLanguage = LANGUAGE_THAI;
498 break;
499 case 0x08 : // Hebrew calendar
500 sCalendar = "[~jewish]";
501 // Many languages (but not all) support Jewish calendar
502 // Unable to find any logic => keep same language
503 break;
504 case 0x0E : // unknown calendar
505 case 0x0F : // unknown calendar
506 case 0x10 : // Indian calendar (unsupported)
507 case 0x11 : // unknown calendar
508 case 0x12 : // unknown calendar
509 case 0x13 : // unknown calendar
510 default : // other calendars (see tdf#36038) are not handle by LibO
511 break;
513 /** Reference language for each numeral ID */
514 static const LanguageType aNumeralIDtoLanguage []=
516 LANGUAGE_DONTKNOW, // 0x00
517 LANGUAGE_ENGLISH_US, // 0x01
518 LANGUAGE_ARABIC_SAUDI_ARABIA, // 0x02 + all Arabic
519 LANGUAGE_FARSI, // 0x03
520 LANGUAGE_HINDI, // 0x04 + Devanagari
521 LANGUAGE_BENGALI, // 0x05
522 LANGUAGE_PUNJABI, // 0x06
523 LANGUAGE_GUJARATI, // 0x07
524 LANGUAGE_ODIA, // 0x08
525 LANGUAGE_TAMIL, // 0x09
526 LANGUAGE_TELUGU, // 0x0A
527 LANGUAGE_KANNADA, // 0x0B
528 LANGUAGE_MALAYALAM, // 0x0C
529 LANGUAGE_THAI, // 0x0D
530 LANGUAGE_LAO, // 0x0E
531 LANGUAGE_TIBETAN, // 0x0F
532 LANGUAGE_BURMESE, // 0x10
533 LANGUAGE_TIGRIGNA_ETHIOPIA, // 0x11
534 LANGUAGE_KHMER, // 0x12
535 LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA, // 0x13
536 LANGUAGE_DONTKNOW, // 0x14
537 LANGUAGE_DONTKNOW, // 0x15
538 LANGUAGE_DONTKNOW, // 0x16
539 LANGUAGE_DONTKNOW, // 0x17
540 LANGUAGE_DONTKNOW, // 0x18
541 LANGUAGE_DONTKNOW, // 0x19
542 LANGUAGE_DONTKNOW, // 0x1A
543 LANGUAGE_JAPANESE, // 0x1B
544 LANGUAGE_JAPANESE, // 0x1C
545 LANGUAGE_JAPANESE, // 0x1D
546 LANGUAGE_CHINESE_SIMPLIFIED, // 0x1E
547 LANGUAGE_CHINESE_SIMPLIFIED, // 0x1F
548 LANGUAGE_CHINESE_SIMPLIFIED, // 0x20
549 LANGUAGE_CHINESE_TRADITIONAL, // 0x21
550 LANGUAGE_CHINESE_TRADITIONAL, // 0x22
551 LANGUAGE_CHINESE_TRADITIONAL, // 0x23
552 LANGUAGE_KOREAN, // 0x24
553 LANGUAGE_KOREAN, // 0x25
554 LANGUAGE_KOREAN, // 0x26
555 LANGUAGE_KOREAN // 0x27
558 sal_uInt16 nNumeralID = aTmpLocale.mnNumeralShape & 0x7F;
559 LanguageType nReferenceLanguage = nNumeralID <= 0x27 ? aNumeralIDtoLanguage[nNumeralID] : LANGUAGE_DONTKNOW;
561 switch ( nNumeralID )
563 // Regular cases: all languages with same primary mask use same numerals
564 case 0x03 : // Perso-Arabic (Farsi) numerals
565 case 0x05 : // Bengali numerals
566 case 0x06 : // Punjabi numerals
567 case 0x07 : // Gujarati numerals
568 case 0x08 : // Odia (Orya) numerals
569 case 0x09 : // Tamil numerals
570 case 0x0A : // Telugu numerals
571 case 0x0B : // Kannada numerals
572 case 0x0C : // Malayalam numerals
573 case 0x0D : // Thai numerals
574 case 0x0E : // Lao numerals
575 case 0x0F : // Tibetan numerals
576 case 0x10 : // Burmese (Myanmar) numerals
577 case 0x11 : // Tigrigna (Ethiopia) numerals
578 case 0x12 : // Khmer numerals
579 if ( primary( nLocaleLang ) != primary( nReferenceLanguage ) )
581 if ( primary( nTmpLocaleLang ) == primary( nReferenceLanguage ) )
583 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
585 else
587 nLang = maLocale.meLanguage = nReferenceLanguage;
590 break;
591 // Special cases
592 case 0x04 : // Devanagari (Hindi) numerals
593 // same numerals (Devanagari) for languages with different primary masks
594 if ( nLocaleLang != LANGUAGE_HINDI && nLocaleLang != LANGUAGE_MARATHI
595 && primary( nLocaleLang ) != primary( LANGUAGE_NEPALI ) )
597 if ( nTmpLocaleLang == LANGUAGE_HINDI || nTmpLocaleLang == LANGUAGE_MARATHI
598 || primary( nTmpLocaleLang ) == primary( LANGUAGE_NEPALI ) )
600 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
602 else
604 nLang = maLocale.meLanguage = LANGUAGE_HINDI;
607 break;
608 case 0x13 : // Mongolian numerals
609 // not all Mongolian languages use Mongolian numerals
610 if ( nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA
611 && nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA
612 && nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_LSO )
614 if ( nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA
615 || nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA
616 || nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_LSO )
618 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
620 else
622 nLang = maLocale.meLanguage = LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA;
625 break;
626 case 0x02 : // Eastern-Arabic numerals
627 // all arabic primary mask + LANGUAGE_PUNJABI_ARABIC_LSO
628 if ( primary( nLocaleLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY
629 && nLocaleLang != LANGUAGE_PUNJABI_ARABIC_LSO )
631 if ( primary( nTmpLocaleLang ) == LANGUAGE_ARABIC_PRIMARY_ONLY
632 || nTmpLocaleLang != LANGUAGE_PUNJABI_ARABIC_LSO )
634 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
636 else
638 nLang = maLocale.meLanguage = nReferenceLanguage;
641 break;
642 // CJK numerals
643 case 0x1B : // simple Asian numerals, Japanese
644 case 0x1C : // financial Asian numerals, Japanese
645 case 0x1D : // Arabic fullwidth numerals, Japanese
646 case 0x24 : // simple Asian numerals, Korean
647 case 0x25 : // financial Asian numerals, Korean
648 case 0x26 : // Arabic fullwidth numerals, Korean
649 case 0x27 : // Korean Hangul numerals
650 // Japanese and Korean are regular
651 if ( primary( nLocaleLang ) != primary( nReferenceLanguage ) )
653 if ( primary( nTmpLocaleLang ) == primary( nReferenceLanguage ) )
655 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
657 else
659 nLang = maLocale.meLanguage = nReferenceLanguage;
662 [[fallthrough]];
663 case 0x1E : // simple Asian numerals, Chinese-PRC
664 case 0x1F : // financial Asian numerals, Chinese-PRC
665 case 0x20 : // Arabic fullwidth numerals, Chinese-PRC
666 case 0x21 : // simple Asian numerals, Chinese-Taiwan
667 case 0x22 : // financial Asian numerals, Chinese-Taiwan
668 case 0x23 : // Arabic fullwidth numerals, Chinese-Taiwan
669 nNatNum = nNumeralID == 0x27 ? 9 : ( ( nNumeralID - 0x1B ) % 3 ) + 1;
670 // [NatNum1] simple numerals
671 // [natNum2] financial numerals
672 // [NatNum3] Arabic fullwidth numerals
673 // Chinese simplified and Chinese traditional have same primary mask
674 // Chinese-PRC
675 if ( nReferenceLanguage == LANGUAGE_CHINESE_SIMPLIFIED
676 && nLocaleLang != LANGUAGE_CHINESE_SIMPLIFIED
677 && nLocaleLang != LANGUAGE_CHINESE_SINGAPORE
678 && nLocaleLang != LANGUAGE_CHINESE_LSO )
680 if ( nTmpLocaleLang == LANGUAGE_CHINESE_SIMPLIFIED
681 || nTmpLocaleLang == LANGUAGE_CHINESE_SINGAPORE
682 || nTmpLocaleLang == LANGUAGE_CHINESE_LSO )
684 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
686 else
688 nLang = maLocale.meLanguage = LANGUAGE_CHINESE_SIMPLIFIED;
691 // Chinese-Taiwan
692 else if ( nReferenceLanguage == LANGUAGE_CHINESE_TRADITIONAL
693 && nLocaleLang != LANGUAGE_CHINESE_TRADITIONAL
694 && nLocaleLang != LANGUAGE_CHINESE_HONGKONG
695 && nLocaleLang != LANGUAGE_CHINESE_MACAU )
697 if ( nTmpLocaleLang == LANGUAGE_CHINESE_TRADITIONAL
698 || nTmpLocaleLang == LANGUAGE_CHINESE_HONGKONG
699 || nTmpLocaleLang == LANGUAGE_CHINESE_MACAU )
701 nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
703 else
705 nLang = maLocale.meLanguage = LANGUAGE_CHINESE_TRADITIONAL;
708 break;
710 if ( nNumeralID >= 0x02 && nNumeralID <= 0x13 )
711 nNatNum = 1;
712 if ( nNatNum )
713 rString.insert(nPos, "[NatNum" + OUString::number(nNatNum) + "]");
714 return sCalendar;
717 namespace
719 bool NatNumTakesParameters(sal_Int16 nNum)
721 return (nNum == css::i18n::NativeNumberMode::NATNUM12);
725 // is there a 3-letter bank code in NatNum12 param (but not
726 // followed by an equal mark, like in the date code "NNN=")?
727 static bool lcl_isNatNum12Currency( std::u16string_view sParam )
729 sal_Int32 nUpper = 0;
730 sal_Int32 nLen = sParam.size();
731 for (sal_Int32 n = 0; n < nLen; ++n)
733 sal_Unicode c = sParam[n];
734 if ( 'A' <= c && c <= 'Z' )
736 ++nUpper;
738 else if ( c == ' ' && nUpper == 3 && (n == 3 || sParam[n - 4] == ' ') )
740 return true;
742 else
744 nUpper = 0;
748 return nUpper == 3 && (nLen == 3 || sParam[nLen - 4] == ' ');
751 SvNumberformat::SvNumberformat(OUString& rString,
752 ImpSvNumberformatScan* pSc,
753 ImpSvNumberInputScan* pISc,
754 const NativeNumberWrapper& rNatNum,
755 sal_Int32& nCheckPos,
756 LanguageType& eLan,
757 bool bReplaceBooleanEquivalent)
758 : rScan(*pSc)
759 , bAdditionalBuiltin( false )
761 if (bReplaceBooleanEquivalent)
762 rScan.ReplaceBooleanEquivalent( rString);
764 OUStringBuffer sBuff(rString);
766 // If the group (AKA thousand) separator is a No-Break Space (French)
767 // replace all occurrences by a simple space.
768 // The same for Narrow No-Break Space just in case some locale uses it.
769 // The tokens will be changed to the LocaleData separator again later on.
770 const OUString& rThSep = GetCurrentLanguageData().GetNumThousandSep();
771 if ( rThSep.getLength() == 1)
773 const sal_Unicode cNBSp = 0xA0;
774 const sal_Unicode cNNBSp = 0x202F;
775 if (rThSep[0] == cNBSp )
776 sBuff.replace( cNBSp, ' ');
777 else if (rThSep[0] == cNNBSp )
778 sBuff.replace( cNNBSp, ' ');
781 OUString aConvertFromDecSep;
782 OUString aConvertToDecSep;
783 if (rScan.GetConvertMode())
785 aConvertFromDecSep = GetCurrentLanguageData().GetNumDecimalSep();
786 maLocale.meLanguage = rScan.GetNewLnge();
787 eLan = maLocale.meLanguage; // Make sure to return switch
789 else
791 maLocale.meLanguage = eLan;
793 bStandard = false;
794 bIsUsed = false;
795 fLimit1 = 0.0;
796 fLimit2 = 0.0;
797 eOp1 = NUMBERFORMAT_OP_NO;
798 eOp2 = NUMBERFORMAT_OP_NO;
799 eType = SvNumFormatType::DEFINED;
801 bool bCancel = false;
802 bool bCondition = false;
803 short eSymbolType;
804 sal_Int32 nPos = 0;
805 sal_Int32 nPosOld;
806 nCheckPos = 0;
808 // Split into 4 sub formats
809 sal_uInt16 nIndex;
810 for ( nIndex = 0; nIndex < 4 && !bCancel; nIndex++ )
812 // Original language/country may have to be reestablished
813 if (rScan.GetConvertMode())
815 rScan.GetCurrentLanguageData().ChangeIntl(rScan.GetTmpLnge());
817 OUString sInsertCalendar; // a calendar resulting from parsing LCID
818 OUString sStr;
819 nPosOld = nPos; // Start position of substring
820 // first get bracketed prefixes; e.g. conditions, color
823 eSymbolType = ImpNextSymbol(sBuff, nPos, sStr);
824 if (eSymbolType > 0) // condition
826 if ( nIndex == 0 && !bCondition )
828 bCondition = true;
829 eOp1 = static_cast<SvNumberformatLimitOps>(eSymbolType);
831 else if ( nIndex == 1 && bCondition )
833 eOp2 = static_cast<SvNumberformatLimitOps>(eSymbolType);
835 else // error
837 bCancel = true; // break for
838 nCheckPos = nPosOld;
840 if (!bCancel)
842 double fNumber;
843 sal_Int32 nCntChars = ImpGetNumber(sBuff, nPos, sStr);
844 if (nCntChars > 0)
846 sal_Int32 nDecPos;
847 SvNumFormatType F_Type = SvNumFormatType::UNDEFINED;
848 if (!pISc->IsNumberFormat(sStr, F_Type, fNumber, nullptr, rNatNum, SvNumInputOptions::NONE) ||
849 ( F_Type != SvNumFormatType::NUMBER &&
850 F_Type != SvNumFormatType::SCIENTIFIC) )
852 fNumber = 0.0;
853 nPos = nPos - nCntChars;
854 sBuff.remove(nPos, nCntChars);
855 sBuff.insert(nPos, '0');
856 nPos++;
858 else if (rScan.GetConvertMode() && ((nDecPos = sStr.indexOf( aConvertFromDecSep)) >= 0))
860 if (aConvertToDecSep.isEmpty())
861 aConvertToDecSep = rScan.GetCurrentLanguageData().GetLangDecimalSep( rScan.GetNewLnge());
862 if (aConvertToDecSep != aConvertFromDecSep)
864 const OUString aStr( sStr.replaceAt( nDecPos,
865 aConvertFromDecSep.getLength(), aConvertToDecSep));
866 nPos = nPos - nCntChars;
867 sBuff.remove(nPos, nCntChars);
868 sBuff.insert(nPos, aStr);
869 nPos += aStr.getLength();
873 else
875 fNumber = 0.0;
876 sBuff.insert(nPos++, '0');
878 if (nIndex == 0)
880 fLimit1 = fNumber;
882 else
884 fLimit2 = fNumber;
886 if ( nPos < sBuff.getLength() && sBuff[nPos] == ']' )
888 nPos++;
890 else
892 bCancel = true; // break for
893 nCheckPos = nPos;
896 nPosOld = nPos; // position before string
898 else if ( lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) )
900 OUString sSymbol( sStr);
901 switch ( eSymbolType )
903 case BRACKET_SYMBOLTYPE_COLOR :
904 if ( NumFor[nIndex].GetColor() != nullptr )
905 { // error, more than one color
906 bCancel = true; // break for
907 nCheckPos = nPosOld;
909 else
911 const Color* pColor = pSc->GetColor( sStr);
912 NumFor[nIndex].SetColor( pColor, sStr);
913 if (pColor == nullptr)
914 { // error
915 bCancel = true; // break for
916 nCheckPos = nPosOld;
919 break;
920 case BRACKET_SYMBOLTYPE_NATNUM0 :
921 case BRACKET_SYMBOLTYPE_NATNUM1 :
922 case BRACKET_SYMBOLTYPE_NATNUM2 :
923 case BRACKET_SYMBOLTYPE_NATNUM3 :
924 case BRACKET_SYMBOLTYPE_NATNUM4 :
925 case BRACKET_SYMBOLTYPE_NATNUM5 :
926 case BRACKET_SYMBOLTYPE_NATNUM6 :
927 case BRACKET_SYMBOLTYPE_NATNUM7 :
928 case BRACKET_SYMBOLTYPE_NATNUM8 :
929 case BRACKET_SYMBOLTYPE_NATNUM9 :
930 case BRACKET_SYMBOLTYPE_NATNUM10 :
931 case BRACKET_SYMBOLTYPE_NATNUM11 :
932 case BRACKET_SYMBOLTYPE_NATNUM12 :
933 case BRACKET_SYMBOLTYPE_NATNUM13 :
934 case BRACKET_SYMBOLTYPE_NATNUM14 :
935 case BRACKET_SYMBOLTYPE_NATNUM15 :
936 case BRACKET_SYMBOLTYPE_NATNUM16 :
937 case BRACKET_SYMBOLTYPE_NATNUM17 :
938 case BRACKET_SYMBOLTYPE_NATNUM18 :
939 case BRACKET_SYMBOLTYPE_NATNUM19 :
940 if ( NumFor[nIndex].GetNatNum().IsSet() )
942 bCancel = true; // break for
943 nCheckPos = nPosOld;
945 else
947 OUString sParams;
948 sal_Int32 nSpacePos = sStr.indexOf(' ');
949 if (nSpacePos >= 0)
951 sParams = o3tl::trim(sStr.subView(nSpacePos+1));
953 //! eSymbolType is negative
954 sal_uInt8 nNum = static_cast<sal_uInt8>(0 - (eSymbolType - BRACKET_SYMBOLTYPE_NATNUM0));
955 if (!sParams.isEmpty() && !NatNumTakesParameters(nNum))
957 bCancel = true; // break for
958 nCheckPos = nPosOld;
959 break;
961 sStr = "NatNum" + OUString::number(nNum);
962 NumFor[nIndex].SetNatNumNum( nNum, false );
963 // NatNum12 supports arguments
964 if (nNum == 12)
966 if (sParams.isEmpty())
967 sParams = "cardinal"; // default NatNum12 format is "cardinal"
968 else if (sParams.indexOf("CURRENCY") >= 0)
969 sParams = sParams.replaceAll("CURRENCY",
970 rLoc().getCurrBankSymbol());
971 NumFor[nIndex].SetNatNumParams(sParams);
972 sStr += " " + sParams;
975 break;
976 case BRACKET_SYMBOLTYPE_DBNUM1 :
977 case BRACKET_SYMBOLTYPE_DBNUM2 :
978 case BRACKET_SYMBOLTYPE_DBNUM3 :
979 case BRACKET_SYMBOLTYPE_DBNUM4 :
980 case BRACKET_SYMBOLTYPE_DBNUM5 :
981 case BRACKET_SYMBOLTYPE_DBNUM6 :
982 case BRACKET_SYMBOLTYPE_DBNUM7 :
983 case BRACKET_SYMBOLTYPE_DBNUM8 :
984 case BRACKET_SYMBOLTYPE_DBNUM9 :
985 if ( NumFor[nIndex].GetNatNum().IsSet() )
987 bCancel = true; // break for
988 nCheckPos = nPosOld;
990 else
992 //! eSymbolType is negative
993 sal_uInt8 nNum = static_cast<sal_uInt8>(1 - (eSymbolType - BRACKET_SYMBOLTYPE_DBNUM1));
994 sStr = "DBNum" + OUStringChar(sal_Unicode('0' + nNum));
995 NumFor[nIndex].SetNatNumNum( nNum, true );
997 break;
998 case BRACKET_SYMBOLTYPE_LOCALE :
999 if ( NumFor[nIndex].GetNatNum().GetLang() != LANGUAGE_DONTKNOW ||
1000 sBuff[nPos-1] != ']' )
1001 // Check also for ']' to avoid pulling in
1002 // locale data for the preview string for not
1003 // yet completed LCIDs in the dialog.
1005 bCancel = true; // break for
1006 nCheckPos = nPosOld;
1008 else
1010 sal_Int32 nTmp = 2;
1011 LocaleType aTmpLocale( ImpGetLocaleType( sStr, nTmp));
1012 if (aTmpLocale.meLanguage == LANGUAGE_DONTKNOW)
1014 bCancel = true; // break for
1015 nCheckPos = nPosOld;
1017 else
1019 // Only the first sub format's locale will be
1020 // used as the format's overall locale.
1021 // Sorts this also under the corresponding
1022 // locale for the dialog.
1023 // If we don't support the locale this would
1024 // result in an unknown (empty) language
1025 // listbox entry and the user would never see
1026 // this format.
1027 if (nIndex == 0 && (aTmpLocale.meLanguage == LANGUAGE_SYSTEM ||
1028 SvNumberFormatter::IsLocaleInstalled( aTmpLocale.meLanguage)))
1030 maLocale = aTmpLocale;
1031 eLan = aTmpLocale.meLanguage; // return to caller
1033 // Set new target locale also at scanner.
1034 // We have to do this because switching locale
1035 // may make replacing keywords and separators
1036 // necessary.
1037 // We can do this because it's the first
1038 // subformat and we're still at parsing the
1039 // modifiers, not keywords.
1040 rScan.SetNewLnge( eLan);
1041 // We can not force conversion though because
1042 // the caller may have explicitly not set it.
1043 // In the usual case the target locale is the
1044 // originating locale the conversion is not
1045 // necessary, when reading alien documents
1046 // conversion is enabled anyway.
1048 /* TODO: fiddle with scanner to make this
1049 * known? A change in the locale may affect
1050 * separators and keywords. On the other
1051 * hand they may have been entered as used
1052 * in the originating locale, there's no
1053 * way to predict other than analyzing the
1054 * format code, we assume here the current
1055 * context is used, which is most likely
1056 * the case.
1057 * */
1059 // Strip a plain locale identifier if locale
1060 // data is available to avoid duplicated
1061 // formats with and without LCID for the same
1062 // locale. Besides it looks ugly and confusing
1063 // and is unnecessary as the format will be
1064 // listed for the resulting locale.
1065 if (aTmpLocale.isPlainLocale())
1066 sStr.clear();
1067 else
1068 sStr = "$-" + aTmpLocale.generateCode();
1070 else
1072 if (nIndex == 0)
1073 // Locale data not available, remember.
1074 maLocale.meLanguageWithoutLocaleData = aTmpLocale.meLanguage;
1076 sStr = "$-" + aTmpLocale.generateCode();
1078 NumFor[nIndex].SetNatNumLang( MsLangId::getRealLanguage( aTmpLocale.meLanguage));
1080 // "$-NNCCLLLL" Numerals and Calendar
1081 if (sSymbol.getLength() > 6)
1083 sInsertCalendar = ImpObtainCalendarAndNumerals( sBuff, nPos, eLan, aTmpLocale);
1085 /* NOTE: there can be only one calendar
1086 * inserted so the last one wins, though
1087 * our own calendar modifiers support
1088 * multiple calendars within one sub format
1089 * code if at different positions. */
1092 break;
1094 if ( !bCancel )
1096 if (sStr == sSymbol)
1098 nPosOld = nPos;
1100 else
1102 sBuff.remove(nPosOld, nPos - nPosOld);
1103 if (!sStr.isEmpty())
1105 sBuff.insert(nPosOld, "[" + sStr + "]");
1106 nPos = nPosOld + sStr.getLength() + 2;
1107 nPosOld = nPos; // position before string
1109 else
1111 nPos = nPosOld; // prefix removed for whatever reason
1117 while ( !bCancel && lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) );
1119 // The remaining format code string
1120 if ( !bCancel )
1122 if (eSymbolType == BRACKET_SYMBOLTYPE_FORMAT)
1124 if (nIndex == 1 && eOp1 == NUMBERFORMAT_OP_NO)
1126 eOp1 = NUMBERFORMAT_OP_GT; // undefined condition, default: > 0
1128 else if (nIndex == 2 && eOp2 == NUMBERFORMAT_OP_NO)
1130 eOp2 = NUMBERFORMAT_OP_LT; // undefined condition, default: < 0
1132 if (sStr.isEmpty())
1134 // Empty sub format.
1135 NumFor[nIndex].Info().eScannedType = SvNumFormatType::EMPTY;
1137 else
1139 if (!sInsertCalendar.isEmpty())
1141 sStr = sInsertCalendar + sStr;
1143 sal_Int32 nStrPos = pSc->ScanFormat( sStr);
1144 sal_uInt16 nCnt = pSc->GetResultStringsCnt();
1145 if (nCnt == 0 && nStrPos == 0) // error
1147 nStrPos = 1;
1149 if (nStrPos == 0) // ok
1151 // e.g. Thai T speciality
1152 if (pSc->GetNatNumModifier() && !NumFor[nIndex].GetNatNum().IsSet())
1154 sStr = "[NatNum" + OUString::number( pSc->GetNatNumModifier()) + "]" + sStr;
1155 NumFor[nIndex].SetNatNumNum( pSc->GetNatNumModifier(), false );
1157 // #i53826# #i42727# For the Thai T speciality we need
1158 // to freeze the locale and immunize it against
1159 // conversions during exports, just in case we want to
1160 // save to Xcl. This disables the feature of being able
1161 // to convert a NatNum to another locale. You can't
1162 // have both.
1163 // FIXME: implement a specialized export conversion
1164 // that works on tokens (have to tokenize all first)
1165 // and doesn't use the format string and
1166 // PutandConvertEntry() to LANGUAGE_ENGLISH_US in
1167 // sc/source/filter/excel/xestyle.cxx
1168 // XclExpNumFmtBuffer::WriteFormatRecord().
1169 LanguageType eLanguage;
1170 if (NumFor[nIndex].GetNatNum().GetNatNum() == 1 &&
1171 ((eLanguage = MsLangId::getRealLanguage( eLan)) == LANGUAGE_THAI) &&
1172 NumFor[nIndex].GetNatNum().GetLang() == LANGUAGE_DONTKNOW)
1174 sStr = "[$-" + OUString::number( sal_uInt16(eLanguage), 16 ).toAsciiUpperCase() + "]" + sStr;
1175 NumFor[nIndex].SetNatNumLang( eLanguage);
1177 sBuff.remove(nPosOld, nPos - nPosOld);
1178 sBuff.insert(nPosOld, sStr);
1179 nPos = nPosOld + sStr.getLength();
1180 if (nPos < sBuff.getLength())
1182 sBuff.insert(nPos, ";");
1183 nPos++;
1185 else if (nIndex > 0)
1187 // The last subformat. If it is a trailing text
1188 // format the omitted subformats act like they were
1189 // not specified and "inherited" the first format,
1190 // e.g. 0;@ behaves like 0;-0;0;@
1191 if (pSc->GetScannedType() == SvNumFormatType::TEXT)
1193 // Reset conditions, reverting any set above.
1194 if (nIndex == 1)
1195 eOp1 = NUMBERFORMAT_OP_NO;
1196 else if (nIndex == 2)
1197 eOp2 = NUMBERFORMAT_OP_NO;
1198 nIndex = 3;
1201 NumFor[nIndex].Enlarge(nCnt);
1202 pSc->CopyInfo(&(NumFor[nIndex].Info()), nCnt);
1203 // type check
1204 if (nIndex == 0)
1206 if ( NumFor[nIndex].GetNatNum().GetNatNum() == 12 &&
1207 lcl_isNatNum12Currency(NumFor[nIndex].GetNatNum().GetParams()) )
1208 eType = SvNumFormatType::CURRENCY;
1209 else
1210 eType = NumFor[nIndex].Info().eScannedType;
1212 else if (nIndex == 3)
1213 { // #77026# Everything recognized IS text
1214 NumFor[nIndex].Info().eScannedType = SvNumFormatType::TEXT;
1216 else if ( NumFor[nIndex].Info().eScannedType != eType)
1218 eType = SvNumFormatType::DEFINED;
1221 else
1223 nCheckPos = nPosOld + nStrPos; // error in string
1224 bCancel = true; // break for
1228 else if (eSymbolType == BRACKET_SYMBOLTYPE_ERROR) // error
1230 nCheckPos = nPosOld;
1231 bCancel = true;
1233 else if ( lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) )
1235 nCheckPos = nPosOld + 1; // error, prefix in string
1236 bCancel = true; // break for
1239 if ( bCancel && !nCheckPos )
1241 nCheckPos = 1; // nCheckPos is used as an error condition
1243 if ( !bCancel )
1245 if ( NumFor[nIndex].GetNatNum().IsSet() &&
1246 NumFor[nIndex].GetNatNum().GetLang() == LANGUAGE_DONTKNOW )
1248 NumFor[nIndex].SetNatNumLang( eLan );
1251 if (sBuff.getLength() == nPos)
1253 if (nIndex < 3 && rString[rString.getLength()-1] == ';')
1255 // A trailing ';' is significant and specifies the following
1256 // subformat to be empty. We don't enter the scanning loop
1257 // above again though.
1258 // Note that the operators apply to the current last scanned
1259 // subformat.
1260 if (nIndex == 0 && eOp1 == NUMBERFORMAT_OP_NO)
1262 eOp1 = NUMBERFORMAT_OP_GT; // undefined condition, default: > 0
1264 else if (nIndex == 1 && eOp2 == NUMBERFORMAT_OP_NO)
1266 eOp2 = NUMBERFORMAT_OP_LT; // undefined condition, default: < 0
1268 NumFor[nIndex+1].Info().eScannedType = SvNumFormatType::EMPTY;
1269 if (sBuff[nPos-1] != ';')
1270 sBuff.insert( nPos++, ';');
1272 if (nIndex == 2 && eSymbolType == BRACKET_SYMBOLTYPE_FORMAT && sBuff[nPos-1] == ';')
1274 // #83510# A 4th subformat explicitly specified to be empty
1275 // hides any text. Need the type here for HasTextFormat()
1276 NumFor[3].Info().eScannedType = SvNumFormatType::TEXT;
1278 bCancel = true;
1280 if ( NumFor[nIndex].GetNatNum().IsSet() )
1282 NumFor[nIndex].SetNatNumDate( bool(NumFor[nIndex].Info().eScannedType & SvNumFormatType::DATE) );
1286 if (!nCheckPos && IsSubstituted())
1288 // For to be substituted formats the scanned type must match the
1289 // substitute type.
1290 if (IsSystemTimeFormat())
1292 if ((eType & ~SvNumFormatType::DEFINED) != SvNumFormatType::TIME)
1293 nCheckPos = std::max<sal_Int32>( sBuff.indexOf(']') + 1, 1);
1295 else if (IsSystemLongDateFormat())
1297 if ((eType & ~SvNumFormatType::DEFINED) != SvNumFormatType::DATE)
1298 nCheckPos = std::max<sal_Int32>( sBuff.indexOf(']') + 1, 1);
1300 else
1301 assert(!"unhandled substitute");
1304 if ( bCondition && !nCheckPos )
1306 if ( nIndex == 1 && NumFor[0].GetCount() == 0 &&
1307 sBuff[sBuff.getLength() - 1] != ';' )
1309 // No format code => GENERAL but not if specified empty
1310 OUString aAdd( pSc->GetStandardName() );
1311 if ( !pSc->ScanFormat( aAdd ) )
1313 sal_uInt16 nCnt = pSc->GetResultStringsCnt();
1314 if ( nCnt )
1316 NumFor[0].Enlarge(nCnt);
1317 pSc->CopyInfo( &(NumFor[0].Info()), nCnt );
1318 sBuff.append(aAdd);
1322 else if ( nIndex == 1 && NumFor[nIndex].GetCount() == 0 &&
1323 sBuff[sBuff.getLength() - 1] != ';' &&
1324 (NumFor[0].GetCount() > 1 ||
1325 (NumFor[0].GetCount() == 1 &&
1326 NumFor[0].Info().nTypeArray[0] != NF_KEY_GENERAL)) )
1328 // No trailing second subformat => GENERAL but not if specified empty
1329 // and not if first subformat is GENERAL
1330 OUString aAdd( pSc->GetStandardName() );
1331 if ( !pSc->ScanFormat( aAdd ) )
1333 sal_uInt16 nCnt = pSc->GetResultStringsCnt();
1334 if ( nCnt )
1336 NumFor[nIndex].Enlarge(nCnt);
1337 pSc->CopyInfo( &(NumFor[nIndex].Info()), nCnt );
1338 sBuff.append(";" + aAdd);
1342 else if ( nIndex == 2 && NumFor[nIndex].GetCount() == 0 &&
1343 sBuff[sBuff.getLength() - 1] != ';' &&
1344 eOp2 != NUMBERFORMAT_OP_NO )
1346 // No trailing third subformat => GENERAL but not if specified empty
1347 OUString aAdd( pSc->GetStandardName() );
1348 if ( !pSc->ScanFormat( aAdd ) )
1350 sal_uInt16 nCnt = pSc->GetResultStringsCnt();
1351 if ( nCnt )
1353 NumFor[nIndex].Enlarge(nCnt);
1354 pSc->CopyInfo( &(NumFor[nIndex].Info()), nCnt );
1355 sBuff.append(";" + aAdd);
1360 rString = sBuff.makeStringAndClear();
1361 sFormatstring = rString;
1363 if (NumFor[2].GetCount() == 0 && // No third partial string
1364 eOp1 == NUMBERFORMAT_OP_GT && eOp2 == NUMBERFORMAT_OP_NO &&
1365 fLimit1 == 0.0 && fLimit2 == 0.0)
1367 eOp1 = NUMBERFORMAT_OP_GE; // Add 0 to the first format
1372 SvNumberformat::~SvNumberformat()
1377 * Next_Symbol
1379 * Splits up the symbols for further processing (by the Turing machine)
1381 * Start state = SsStart, * = Special state
1382 * ---------------+-------------------+----------------------------+---------------
1383 * Old State | Symbol read | Event | New state
1384 * ---------------+-------------------+----------------------------+---------------
1385 * SsStart | " | Symbol += Character | SsGetQuoted
1386 * | ; | Pos-- | SsGetString
1387 * | [ | Symbol += Character | SsGetBracketed
1388 * | ] | Error | SsStop
1389 * | BLANK | |
1390 * | Else | Symbol += Character | SsGetString
1391 * ---------------+-------------------+----------------------------+---------------
1392 * SsGetString | " | Symbol += Character | SsGetQuoted
1393 * | ; | | SsStop
1394 * | Else | Symbol += Character |
1395 * ---------------+-------------------+----------------------------+---------------
1396 * SsGetQuoted | " | Symbol += Character | SsGetString
1397 * | Else | Symbol += Character |
1398 * ---------------+-------------------+----------------------------+---------------
1399 * SsGetBracketed | <, > = | del [ |
1400 * | | Symbol += Character | SsGetCon
1401 * | BLANK | |
1402 * | h, H, m, M, s, S | Symbol += Character | SsGetTime
1403 * | Else | del [ |
1404 * | | Symbol += Character | SsGetPrefix
1405 * ---------------+-------------------+----------------------------+---------------
1406 * SsGetTime | ] | Symbol += Character | SsGetString
1407 * | h, H, m, M, s, S | Symbol += Character, * | SsGetString
1408 * | Else | del [; Symbol += Character | SsGetPrefix
1409 * ---------------+-------------------+----------------------------+---------------
1410 * SsGetPrefix | ] | | SsStop
1411 * | Else | Symbol += Character |
1412 * ---------------+-------------------+----------------------------+---------------
1413 * SsGetCon | >, = | Symbol += Character |
1414 * | ] | | SsStop
1415 * | Else | Error | SsStop
1416 * ---------------+-------------------+----------------------------+---------------
1419 namespace {
1421 enum ScanState
1423 SsStop,
1424 SsStart,
1425 SsGetCon, // condition
1426 SsGetString, // format string
1427 SsGetPrefix, // color or NatNumN
1428 SsGetTime, // [HH] for time
1429 SsGetBracketed, // any [...] not decided yet
1430 SsGetQuoted // quoted text
1435 // read a string until ']' and delete spaces in input
1436 // static
1437 sal_Int32 SvNumberformat::ImpGetNumber(OUStringBuffer& rString,
1438 sal_Int32& nPos,
1439 OUString& sSymbol)
1441 sal_Int32 nStartPos = nPos;
1442 sal_Unicode cToken;
1443 sal_Int32 nLen = rString.getLength();
1444 OUStringBuffer sBuffSymbol;
1445 while ( nPos < nLen )
1447 cToken = rString[nPos];
1448 if (cToken == ']')
1449 break;
1450 if (cToken == ' ')
1451 { // delete spaces
1452 rString.remove(nPos,1);
1453 nLen--;
1455 else
1457 nPos++;
1458 sBuffSymbol.append(cToken);
1461 sSymbol = sBuffSymbol.makeStringAndClear();
1462 return nPos - nStartPos;
1465 namespace {
1467 sal_Unicode toUniChar(sal_uInt8 n)
1469 if (n < 10)
1470 return static_cast<sal_Unicode>('0' + n);
1471 else
1472 return static_cast<sal_Unicode>('A' + n - 10);
1475 bool IsCombiningSymbol( OUStringBuffer& rStringBuffer, sal_Int32 nPos )
1477 bool bRet = false;
1478 while (nPos >= 0)
1480 switch (rStringBuffer[nPos])
1482 case '*':
1483 case '\\':
1484 case '_':
1485 bRet = !bRet;
1486 --nPos;
1487 break;
1488 default:
1489 return bRet;
1492 return bRet;
1495 } // namespace
1497 OUString SvNumberformat::LocaleType::generateCode() const
1499 OUStringBuffer aBuf;
1500 #if 0
1501 // TODO: We may re-enable this later. Don't remove it! --Kohei
1502 if (mnNumeralShape)
1504 sal_uInt8 nVal = mnNumeralShape;
1505 for (sal_uInt8 i = 0; i < 2; ++i)
1507 sal_uInt8 n = (nVal & 0xF0) >> 4;
1508 if (n || aBuf.getLength())
1510 aBuf.append(toUniChar(n));
1512 nVal = nVal << 4;
1516 if (mnNumeralShape || mnCalendarType)
1518 sal_uInt8 nVal = mnCalendarType;
1519 for (sal_uInt8 i = 0; i < 2; ++i)
1521 sal_uInt8 n = (nVal & 0xF0) >> 4;
1522 if (n || aBuf.getLength())
1524 aBuf.append(toUniChar(n));
1526 nVal = nVal << 4;
1529 #endif
1531 sal_uInt16 n16 = static_cast<sal_uInt16>(
1532 (meLanguageWithoutLocaleData == LANGUAGE_DONTKNOW) ? meLanguage :
1533 meLanguageWithoutLocaleData);
1534 if (meLanguage == LANGUAGE_SYSTEM)
1536 switch (meSubstitute)
1538 case Substitute::NONE:
1539 ; // nothing
1540 break;
1541 case Substitute::TIME:
1542 n16 = static_cast<sal_uInt16>(LANGUAGE_NF_SYSTEM_TIME);
1543 break;
1544 case Substitute::LONGDATE:
1545 n16 = static_cast<sal_uInt16>(LANGUAGE_NF_SYSTEM_DATE);
1546 break;
1549 for (sal_uInt8 i = 0; i < 4; ++i)
1551 sal_uInt8 n = static_cast<sal_uInt8>((n16 & 0xF000) >> 12);
1552 // Omit leading zeros for consistency.
1553 if (n || !aBuf.isEmpty() || i == 3)
1555 aBuf.append(toUniChar(n));
1557 n16 = (n16 << 4) & 0xFFFF;
1560 return aBuf.makeStringAndClear();
1563 SvNumberformat::LocaleType::LocaleType()
1564 : meLanguage(LANGUAGE_DONTKNOW)
1565 , meLanguageWithoutLocaleData(LANGUAGE_DONTKNOW)
1566 , meSubstitute(Substitute::NONE)
1567 , mnNumeralShape(0)
1568 , mnCalendarType(0)
1572 SvNumberformat::LocaleType::LocaleType(sal_uInt32 nRawNum)
1573 : meLanguage(LANGUAGE_DONTKNOW)
1574 , meLanguageWithoutLocaleData(LANGUAGE_DONTKNOW)
1575 , meSubstitute(Substitute::NONE)
1576 , mnNumeralShape(0)
1577 , mnCalendarType(0)
1579 meLanguage = static_cast<LanguageType>(nRawNum & 0x0000FFFF);
1580 if (meLanguage == LANGUAGE_NF_SYSTEM_TIME)
1582 meSubstitute = Substitute::TIME;
1583 meLanguage = LANGUAGE_SYSTEM;
1585 else if (meLanguage == LANGUAGE_NF_SYSTEM_DATE)
1587 meSubstitute = Substitute::LONGDATE;
1588 meLanguage = LANGUAGE_SYSTEM;
1590 nRawNum = (nRawNum >> 16);
1591 mnCalendarType = static_cast<sal_uInt8>(nRawNum & 0xFF);
1592 nRawNum = (nRawNum >> 8);
1593 mnNumeralShape = static_cast<sal_uInt8>(nRawNum & 0xFF);
1596 bool SvNumberformat::LocaleType::isPlainLocale() const
1598 return meSubstitute == Substitute::NONE && !mnCalendarType && !mnNumeralShape;
1601 // static
1602 SvNumberformat::LocaleType SvNumberformat::ImpGetLocaleType(std::u16string_view rString, sal_Int32& nPos )
1604 sal_uInt32 nNum = 0;
1605 sal_Unicode cToken = 0;
1606 sal_Int32 nStart = nPos;
1607 sal_Int32 nLen = rString.size();
1608 while ( nPos < nLen && (nPos - nStart < 8) )
1610 cToken = rString[nPos];
1611 if (cToken == ']')
1612 break;
1613 if ( '0' <= cToken && cToken <= '9' )
1615 nNum *= 16;
1616 nNum += cToken - '0';
1618 else if ( 'a' <= cToken && cToken <= 'f' )
1620 nNum *= 16;
1621 nNum += cToken - 'a' + 10;
1623 else if ( 'A' <= cToken && cToken <= 'F' )
1625 nNum *= 16;
1626 nNum += cToken - 'A' + 10;
1628 else
1630 return LocaleType(); // LANGUAGE_DONTKNOW;
1632 ++nPos;
1635 return (cToken == ']' || nPos == nLen) ? LocaleType(nNum) : LocaleType();
1638 static bool lcl_matchKeywordAndGetNumber( std::u16string_view rString, const sal_Int32 nPos,
1639 std::u16string_view rKeyword, sal_Int32 & nNumber )
1641 if (0 <= nPos && nPos + static_cast<sal_Int32>(rKeyword.size()) < static_cast<sal_Int32>(rString.size()) && o3tl::matchIgnoreAsciiCase( rString, rKeyword, nPos))
1643 nNumber = o3tl::toInt32(rString.substr( nPos + rKeyword.size()));
1644 return true;
1646 else
1648 nNumber = 0;
1649 return false;
1653 short SvNumberformat::ImpNextSymbol(OUStringBuffer& rString,
1654 sal_Int32& nPos,
1655 OUString& sSymbol) const
1657 short eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
1658 sal_Unicode cToken;
1659 sal_Unicode cLetter = ' '; // Preliminary result
1660 sal_Int32 nLen = rString.getLength();
1661 ScanState eState = SsStart;
1662 OUStringBuffer sBuffSymbol(128);
1664 const NfKeywordTable & rKeywords = rScan.GetKeywords();
1665 while (nPos < nLen && eState != SsStop)
1667 cToken = rString[nPos];
1668 nPos++;
1669 switch (eState)
1671 case SsStart:
1672 if (cToken == '\"')
1674 eState = SsGetQuoted;
1675 sBuffSymbol.append(cToken);
1677 else if (cToken == '[')
1679 eState = SsGetBracketed;
1680 sBuffSymbol.append(cToken);
1682 else if (cToken == ';')
1684 eState = SsGetString;
1685 nPos--;
1686 eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
1688 else if (cToken == ']')
1690 eState = SsStop;
1691 eSymbolType = BRACKET_SYMBOLTYPE_ERROR;
1693 else if (cToken == ' ') // Skip Blanks
1695 nPos--;
1696 rString.remove(nPos, 1);
1697 nLen--;
1699 else
1701 sBuffSymbol.append(cToken);
1702 eState = SsGetString;
1703 eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
1705 break;
1706 case SsGetBracketed:
1707 switch (cToken)
1709 case '<':
1710 case '>':
1711 case '=':
1712 sBuffSymbol.stripStart('[');
1713 sBuffSymbol.append(cToken);
1714 cLetter = cToken;
1715 eState = SsGetCon;
1716 switch (cToken)
1718 case '<':
1719 eSymbolType = NUMBERFORMAT_OP_LT;
1720 break;
1721 case '>':
1722 eSymbolType = NUMBERFORMAT_OP_GT;
1723 break;
1724 case '=':
1725 eSymbolType = NUMBERFORMAT_OP_EQ;
1726 break;
1728 break;
1729 case ' ':
1730 nPos--;
1731 rString.remove(nPos, 1);
1732 nLen--;
1733 break;
1734 case '$' :
1735 if ( nPos < nLen && rString[nPos] == '-' )
1737 // [$-xxx] locale
1738 sBuffSymbol.stripStart('[');
1739 eSymbolType = BRACKET_SYMBOLTYPE_LOCALE;
1740 eState = SsGetPrefix;
1742 else
1743 { // currency
1744 eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
1745 eState = SsGetString;
1747 sBuffSymbol.append(cToken);
1748 break;
1749 case '~' :
1750 // calendarID
1751 eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
1752 sBuffSymbol.append(cToken);
1753 eState = SsGetString;
1754 break;
1755 default:
1757 static constexpr OUString aNatNum(u"NATNUM"_ustr);
1758 static constexpr OUString aDBNum(u"DBNUM"_ustr);
1759 const OUString aBufStr( rString.toString());
1760 sal_Int32 nNatNumNum;
1761 sal_Int32 nDBNum;
1762 if ( lcl_matchKeywordAndGetNumber( aBufStr, nPos-1, aNatNum, nNatNumNum) &&
1763 0 <= nNatNumNum && nNatNumNum <= 19 )
1765 sBuffSymbol.stripStart('[');
1766 sBuffSymbol.append( aBufStr.subView(--nPos, aNatNum.getLength()+1) );
1767 nPos += aNatNum.getLength()+1;
1768 //! SymbolType is negative
1769 eSymbolType = static_cast<short>(BRACKET_SYMBOLTYPE_NATNUM0 - nNatNumNum);
1770 eState = SsGetPrefix;
1772 else if ( lcl_matchKeywordAndGetNumber( aBufStr, nPos-1, aDBNum, nDBNum) &&
1773 1 <= nDBNum && nDBNum <= 9 )
1775 sBuffSymbol.stripStart('[');
1776 sBuffSymbol.append( aBufStr.subView(--nPos, aDBNum.getLength()+1) );
1777 nPos += aDBNum.getLength()+1;
1778 //! SymbolType is negative
1779 eSymbolType = sal::static_int_cast< short >( BRACKET_SYMBOLTYPE_DBNUM1 - (nDBNum - 1) );
1780 eState = SsGetPrefix;
1782 else
1784 sal_Unicode cUpper = rChrCls().uppercase( aBufStr, nPos-1, 1)[0];
1785 if ( cUpper == rKeywords[NF_KEY_H][0] || // H
1786 cUpper == rKeywords[NF_KEY_MI][0] || // M
1787 cUpper == rKeywords[NF_KEY_S][0] ) // S
1789 sBuffSymbol.append(cToken);
1790 eState = SsGetTime;
1791 cLetter = cToken;
1793 else
1795 sBuffSymbol.stripStart('[');
1796 sBuffSymbol.append(cToken);
1797 eSymbolType = BRACKET_SYMBOLTYPE_COLOR;
1798 eState = SsGetPrefix;
1803 break;
1804 case SsGetString:
1805 if (cToken == '\"')
1807 eState = SsGetQuoted;
1808 sBuffSymbol.append(cToken);
1810 else if (cToken == ';' && (nPos < 2 || !IsCombiningSymbol( rString, nPos-2)))
1812 eState = SsStop;
1814 else
1816 sBuffSymbol.append(cToken);
1818 break;
1819 case SsGetQuoted:
1820 if (cToken == '\"')
1822 eState = SsGetString;
1823 sBuffSymbol.append(cToken);
1825 else
1827 sBuffSymbol.append(cToken);
1829 break;
1830 case SsGetTime:
1831 if (cToken == ']')
1833 sBuffSymbol.append(cToken);
1834 eState = SsGetString;
1835 eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
1837 else
1839 sal_Unicode cUpper = rChrCls().uppercase(rString.toString(), nPos-1, 1)[0];
1840 if (cUpper == rKeywords[NF_KEY_H][0] || // H
1841 cUpper == rKeywords[NF_KEY_MI][0] || // M
1842 cUpper == rKeywords[NF_KEY_S][0] ) // S
1844 if (cLetter == cToken)
1846 sBuffSymbol.append(cToken);
1847 cLetter = ' ';
1849 else
1851 sBuffSymbol.stripStart('[');
1852 sBuffSymbol.append(cToken);
1853 eState = SsGetPrefix;
1856 else
1858 sBuffSymbol.stripStart('[');
1859 sBuffSymbol.append(cToken);
1860 eSymbolType = BRACKET_SYMBOLTYPE_COLOR;
1861 eState = SsGetPrefix;
1864 break;
1865 case SsGetCon:
1866 switch (cToken)
1868 case '<':
1869 eState = SsStop;
1870 eSymbolType = BRACKET_SYMBOLTYPE_ERROR;
1871 break;
1872 case '>':
1873 if (cLetter == '<')
1875 sBuffSymbol.append(cToken);
1876 cLetter = ' ';
1877 eState = SsStop;
1878 eSymbolType = NUMBERFORMAT_OP_NE;
1880 else
1882 eState = SsStop;
1883 eSymbolType = BRACKET_SYMBOLTYPE_ERROR;
1885 break;
1886 case '=':
1887 if (cLetter == '<')
1889 sBuffSymbol.append(cToken);
1890 cLetter = ' ';
1891 eSymbolType = NUMBERFORMAT_OP_LE;
1893 else if (cLetter == '>')
1895 sBuffSymbol.append(cToken);
1896 cLetter = ' ';
1897 eSymbolType = NUMBERFORMAT_OP_GE;
1899 else
1901 eState = SsStop;
1902 eSymbolType = BRACKET_SYMBOLTYPE_ERROR;
1904 break;
1905 case ' ':
1906 nPos--;
1907 rString.remove(nPos,1);
1908 nLen--;
1909 break;
1910 default:
1911 eState = SsStop;
1912 nPos--;
1913 break;
1915 break;
1916 case SsGetPrefix:
1917 if (cToken == ']')
1919 eState = SsStop;
1921 else
1923 sBuffSymbol.append(cToken);
1925 break;
1926 default:
1927 break;
1928 } // of switch
1929 } // of while
1930 sSymbol = sBuffSymbol.makeStringAndClear();
1931 return eSymbolType;
1934 void SvNumberformat::ConvertLanguage( SvNumberFormatter& rConverter,
1935 LanguageType eConvertFrom,
1936 LanguageType eConvertTo )
1938 sal_Int32 nCheckPos;
1939 sal_uInt32 nKey;
1940 SvNumFormatType nType = eType;
1941 OUString aFormatString( sFormatstring );
1942 rConverter.PutandConvertEntry( aFormatString, nCheckPos, nType,
1943 nKey, eConvertFrom, eConvertTo, false);
1944 const SvNumberformat* pFormat = rConverter.GetEntry( nKey );
1945 DBG_ASSERT( pFormat, "SvNumberformat::ConvertLanguage: Conversion without format" );
1946 if ( pFormat )
1948 ImpCopyNumberformat( *pFormat );
1949 // Reset values taken over from Formatter/Scanner
1950 // pColor still points to table in temporary Formatter/Scanner
1951 for (ImpSvNumFor & rFormatter : NumFor)
1953 OUString aColorName = rFormatter.GetColorName();
1954 const Color* pColor = rScan.GetColor( aColorName );
1955 rFormatter.SetColor( pColor, aColorName );
1960 bool SvNumberformat::HasNewCurrency() const
1962 for (const auto & j : NumFor)
1964 if ( j.HasNewCurrency() )
1966 return true;
1969 return false;
1972 bool SvNumberformat::GetNewCurrencySymbol( OUString& rSymbol,
1973 OUString& rExtension ) const
1975 for (const auto & j : NumFor)
1977 if ( j.GetNewCurrencySymbol( rSymbol, rExtension ) )
1979 return true;
1982 rSymbol.clear();
1983 rExtension.clear();
1984 return false;
1987 // static
1988 OUString SvNumberformat::StripNewCurrencyDelimiters( const OUString& rStr )
1990 OUStringBuffer aTmp(rStr.getLength());
1991 sal_Int32 nStartPos, nPos, nLen;
1992 nLen = rStr.getLength();
1993 nStartPos = 0;
1994 while ( (nPos = rStr.indexOf( "[$", nStartPos )) >= 0 )
1996 sal_Int32 nEnd;
1997 if ( (nEnd = GetQuoteEnd( rStr, nPos )) >= 0 )
1999 aTmp.append(rStr.subView( nStartPos, ++nEnd - nStartPos ));
2000 nStartPos = nEnd;
2002 else
2004 aTmp.append(rStr.subView(nStartPos, nPos - nStartPos) );
2005 nStartPos = nPos + 2;
2006 sal_Int32 nDash;
2007 nEnd = nStartPos - 1;
2010 nDash = rStr.indexOf( '-', ++nEnd );
2011 nEnd = GetQuoteEnd( rStr, nDash );
2013 while ( nEnd >= 0 );
2014 sal_Int32 nClose;
2015 nEnd = nStartPos - 1;
2018 nClose = rStr.indexOf( ']', ++nEnd );
2019 nEnd = GetQuoteEnd( rStr, nClose );
2021 while ( nEnd >= 0 );
2023 if(nClose < 0)
2025 /* there should always be a closing ]
2026 * but the old String class would have hidden
2027 * that. so be conservative too
2029 nClose = nLen;
2032 nPos = nClose;
2033 if(nDash >= 0 && nDash < nClose)
2035 nPos = nDash;
2037 aTmp.append(rStr.subView(nStartPos, nPos - nStartPos) );
2038 nStartPos = nClose + 1;
2041 if ( nLen > nStartPos )
2043 aTmp.append(rStr.subView(nStartPos, nLen - nStartPos) );
2045 return aTmp.makeStringAndClear();
2048 void SvNumberformat::ImpGetOutputStandard(double& fNumber, OUStringBuffer& rOutString,
2049 const NativeNumberWrapper& rNatNum) const
2051 OUString sTemp;
2052 ImpGetOutputStandard(fNumber, sTemp, rNatNum);
2053 rOutString = sTemp;
2056 void SvNumberformat::ImpGetOutputStandard(double& fNumber, OUString& rOutString,
2057 const NativeNumberWrapper& rNatNum) const
2059 sal_uInt16 nStandardPrec = rScan.GetStandardPrec();
2061 if ( fabs(fNumber) > EXP_ABS_UPPER_BOUND )
2063 nStandardPrec = ::std::min(nStandardPrec, static_cast<sal_uInt16>(14)); // limits to 14 decimals
2064 rOutString = ::rtl::math::doubleToUString( fNumber,
2065 rtl_math_StringFormat_E2, nStandardPrec /*2*/,
2066 GetCurrentLanguageData().GetNumDecimalSep()[0]);
2068 else
2070 ImpGetOutputStdToPrecision(fNumber, rOutString, nStandardPrec, rNatNum);
2074 namespace
2077 template<typename T>
2078 bool checkForAll0s(const T& rString, sal_Int32 nIdx=0)
2080 if (nIdx>=rString.getLength())
2081 return false;
2085 if (rString[nIdx]!='0')
2086 return false;
2088 while (++nIdx<rString.getLength());
2090 return true;
2093 OUString impTransliterateImpl(const OUString& rStr,
2094 const SvNumberNatNum& rNum,
2095 const NativeNumberWrapper& rNatNum)
2097 css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() );
2098 return rNatNum.getNativeNumberStringParams(rStr, aLocale, rNum.GetNatNum(), rNum.GetParams());
2101 void impTransliterateImpl(OUStringBuffer& rStr,
2102 const SvNumberNatNum& rNum,
2103 const NativeNumberWrapper& rNatNum)
2105 css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() );
2107 rStr = rNatNum.getNativeNumberStringParams(
2108 OUString::unacquired(rStr), aLocale, rNum.GetNatNum(), rNum.GetParams());
2111 OUString impTransliterate(const OUString& rStr, const SvNumberNatNum& rNum, const NativeNumberWrapper& rNatNum)
2113 return rNum.IsComplete() ? impTransliterateImpl(rStr, rNum, rNatNum) : rStr;
2116 void impTransliterate(OUStringBuffer& rStr, const SvNumberNatNum& rNum, const NativeNumberWrapper& rNatNum)
2118 if(rNum.IsComplete())
2120 impTransliterateImpl(rStr, rNum, rNatNum);
2126 void SvNumberformat::ImpGetOutputStdToPrecision(double& rNumber, OUString& rOutString, sal_uInt16 nPrecision,
2127 const NativeNumberWrapper& rNatNum) const
2129 // Make sure the precision doesn't go over the maximum allowable precision.
2130 nPrecision = ::std::min(UPPER_PRECISION, nPrecision);
2132 // We decided to strip trailing zeros unconditionally, since binary
2133 // double-precision rounding error makes it impossible to determine e.g.
2134 // whether 844.10000000000002273737 is what the user has typed, or the
2135 // user has typed 844.1 but IEEE 754 represents it that way internally.
2137 rOutString = ::rtl::math::doubleToUString( rNumber,
2138 rtl_math_StringFormat_F, nPrecision /*2*/,
2139 GetCurrentLanguageData().GetNumDecimalSep()[0], true );
2140 if (rOutString[0] == '-' && checkForAll0s(rOutString, 1))
2142 rOutString = comphelper::string::stripStart(rOutString, '-'); // not -0
2144 rOutString = ::impTransliterate(rOutString, NumFor[0].GetNatNum(), rNatNum);
2147 void SvNumberformat::ImpGetOutputInputLine(double fNumber, OUString& OutString) const
2149 bool bModified = false;
2150 if ( (eType & SvNumFormatType::PERCENT) && (fabs(fNumber) < D_MAX_D_BY_100))
2152 if (fNumber == 0.0)
2154 OutString = "0%";
2155 return;
2157 fNumber *= 100;
2158 bModified = true;
2161 if (fNumber == 0.0)
2163 OutString = "0";
2164 return;
2167 OutString = ::rtl::math::doubleToUString( fNumber,
2168 rtl_math_StringFormat_Automatic,
2169 rtl_math_DecimalPlaces_Max,
2170 GetCurrentLanguageData().GetNumDecimalSep()[0], true );
2172 if ( eType & SvNumFormatType::PERCENT && bModified)
2174 OutString += "%";
2178 short SvNumberformat::ImpCheckCondition(double fNumber,
2179 double fLimit,
2180 SvNumberformatLimitOps eOp)
2182 switch(eOp)
2184 case NUMBERFORMAT_OP_NO:
2185 return -1;
2186 case NUMBERFORMAT_OP_EQ:
2187 return static_cast<short>(fNumber == fLimit);
2188 case NUMBERFORMAT_OP_NE:
2189 return static_cast<short>(fNumber != fLimit);
2190 case NUMBERFORMAT_OP_LT:
2191 return static_cast<short>(fNumber < fLimit);
2192 case NUMBERFORMAT_OP_LE:
2193 return static_cast<short>(fNumber <= fLimit);
2194 case NUMBERFORMAT_OP_GT:
2195 return static_cast<short>(fNumber > fLimit);
2196 case NUMBERFORMAT_OP_GE:
2197 return static_cast<short>(fNumber >= fLimit);
2198 default:
2199 return -1;
2203 static bool lcl_appendStarFillChar( OUStringBuffer& rBuf, std::u16string_view rStr )
2205 // Right during user input the star symbol is the very
2206 // last character before the user enters another one.
2207 if (rStr.size() > 1)
2209 rBuf.append(u'\x001B');
2210 rBuf.append(rStr[1]);
2211 return true;
2213 return false;
2216 static bool lcl_insertStarFillChar( OUStringBuffer& rBuf, sal_Int32 nPos, std::u16string_view rStr )
2218 if (rStr.size() > 1)
2220 rBuf.insert( nPos, rStr[1]);
2221 rBuf.insert( nPos, u'\x001B');
2222 return true;
2224 return false;
2227 void SvNumberformat::GetOutputString(std::u16string_view sString,
2228 OUString& OutString,
2229 const Color** ppColor,
2230 bool bStarFlag) const
2232 OUStringBuffer sOutBuff;
2233 sal_uInt16 nIx;
2234 if (eType & SvNumFormatType::TEXT)
2236 nIx = 0;
2238 else if (NumFor[3].GetCount() > 0)
2240 nIx = 3;
2242 else
2244 *ppColor = nullptr; // no change of color
2245 return;
2247 *ppColor = NumFor[nIx].GetColor();
2248 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
2249 if (rInfo.eScannedType == SvNumFormatType::TEXT)
2251 const sal_uInt16 nCnt = NumFor[nIx].GetCount();
2252 for (sal_uInt16 i = 0; i < nCnt; i++)
2254 switch (rInfo.nTypeArray[i])
2256 case NF_SYMBOLTYPE_STAR:
2257 if( bStarFlag )
2259 lcl_appendStarFillChar( sOutBuff, rInfo.sStrArray[i]);
2261 break;
2262 case NF_SYMBOLTYPE_BLANK:
2263 if (rInfo.sStrArray[i].getLength() >= 2)
2264 InsertBlanks( sOutBuff, sOutBuff.getLength(), rInfo.sStrArray[i][1] );
2265 break;
2266 case NF_KEY_GENERAL : // #77026# "General" is the same as "@"
2267 case NF_SYMBOLTYPE_DEL :
2268 sOutBuff.append(sString);
2269 break;
2270 default:
2271 sOutBuff.append(rInfo.sStrArray[i]);
2275 OutString = sOutBuff.makeStringAndClear();
2278 namespace {
2280 void lcl_GetOutputStringScientific(double fNumber, sal_uInt16 nCharCount,
2281 const SvNFLanguageData& rLanguageData, OUString& rOutString)
2283 bool bSign = std::signbit(fNumber);
2285 // 1.000E+015 (one digit and the decimal point, and the two chars +
2286 // nExpDigit for the exponential part, totalling 6 or 7).
2287 double fExp = log10( fabs(fNumber) );
2288 if( fExp < 0.0 )
2289 fExp = 1.0 - fExp;
2290 sal_uInt16 nCharFormat = 6 + (fExp >= 100.0 ? 1 : 0);
2291 sal_uInt16 nPrec = nCharCount > nCharFormat ? nCharCount - nCharFormat : 0;
2292 if (nPrec && bSign)
2294 // Make room for the negative sign.
2295 --nPrec;
2297 nPrec = ::std::min(nPrec, static_cast<sal_uInt16>(14)); // limit to 14 decimals.
2299 rOutString = ::rtl::math::doubleToUString(fNumber, rtl_math_StringFormat_E2,
2300 nPrec, rLanguageData.GetNumDecimalSep()[0], true );
2303 OUString lcl_GetPercentString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt)
2305 sal_Int32 i;
2306 OUStringBuffer aPercentString;
2307 for( i = 0; i < nCnt; i++ )
2309 if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_PERCENT )
2311 aPercentString.append( rInfo.sStrArray[i] );
2312 bool bStringFound = false;
2313 for( i--; i >= 0 && rInfo.nTypeArray[i] == NF_SYMBOLTYPE_STRING ; i-- )
2315 if( !bStringFound )
2317 bStringFound = true;
2318 aPercentString.insert( 0, "\"" );
2320 aPercentString.insert( 0, rInfo.sStrArray[i] );
2322 i = nCnt;
2323 if( bStringFound )
2324 aPercentString.insert( 0, "\"" );
2327 return aPercentString.makeStringAndClear();
2330 OUString lcl_GetDenominatorString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt)
2332 sal_Int32 i;
2333 OUStringBuffer aDenominatorString;
2334 for( i = 0; i < nCnt; i++ )
2336 if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC )
2338 while ( ( ++i < nCnt ) && rInfo.nTypeArray[i] != NF_SYMBOLTYPE_FRAC_FDIV
2339 && rInfo.nTypeArray[i] != NF_SYMBOLTYPE_DIGIT );
2340 for( ; i < nCnt; i++ )
2342 if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC_FDIV || rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT )
2343 aDenominatorString.append( rInfo.sStrArray[i] );
2344 else
2345 i = nCnt;
2349 return aDenominatorString.makeStringAndClear();
2352 OUString lcl_GetNumeratorString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt)
2354 sal_Int32 i;
2355 OUStringBuffer aNumeratorString;
2356 for( i = 0; i < nCnt; i++ )
2358 if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC )
2360 for( i--; i >= 0 && rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT ; i-- )
2362 aNumeratorString.insert( 0, rInfo.sStrArray[i] );
2364 i = nCnt;
2367 return aNumeratorString.makeStringAndClear();
2370 OUString lcl_GetFractionIntegerString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt)
2372 sal_Int32 i;
2373 OUStringBuffer aIntegerString;
2374 for( i = 0; i < nCnt; i++ )
2376 if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRACBLANK )
2378 for( i--; i >= 0 && ( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT
2379 || rInfo.nTypeArray[i] == NF_SYMBOLTYPE_THSEP ); i-- )
2381 aIntegerString.insert( 0, rInfo.sStrArray[i] );
2383 i = nCnt;
2386 return aIntegerString.makeStringAndClear();
2389 OUString lcl_GetIntegerFractionDelimiterString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt)
2391 sal_uInt16 i;
2392 for( i = 0; i < nCnt; i++ )
2394 if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRACBLANK )
2396 return rInfo.sStrArray[i];
2399 return OUString();
2404 OUString SvNumberformat::GetPercentString( sal_uInt16 nNumFor ) const
2406 const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info();
2407 sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
2408 return lcl_GetPercentString( rInfo, nCnt );
2411 OUString SvNumberformat::GetDenominatorString( sal_uInt16 nNumFor ) const
2413 const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info();
2414 sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
2415 return lcl_GetDenominatorString( rInfo, nCnt );
2418 OUString SvNumberformat::GetNumeratorString( sal_uInt16 nNumFor ) const
2420 const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info();
2421 sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
2422 return lcl_GetNumeratorString( rInfo, nCnt );
2425 OUString SvNumberformat::GetIntegerFractionDelimiterString( sal_uInt16 nNumFor ) const
2427 const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info();
2428 sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
2429 return lcl_GetIntegerFractionDelimiterString( rInfo, nCnt );
2432 bool SvNumberformat::GetOutputString(double fNumber, sal_uInt16 nCharCount, OUString& rOutString, const NativeNumberWrapper& rNatNum) const
2434 if (eType != SvNumFormatType::NUMBER)
2436 return false;
2438 double fTestNum = fNumber;
2439 bool bSign = std::signbit(fTestNum);
2440 if (bSign)
2442 fTestNum = -fTestNum;
2444 if (fTestNum < EXP_LOWER_BOUND)
2446 lcl_GetOutputStringScientific(fNumber, nCharCount, GetCurrentLanguageData(), rOutString);
2447 return true;
2450 double fExp = log10(fTestNum);
2451 // Values < 1.0 always have one digit before the decimal point.
2452 sal_uInt16 nDigitPre = fExp >= 0.0 ? static_cast<sal_uInt16>(ceil(fExp)) : 1;
2454 if (nDigitPre > 15)
2456 lcl_GetOutputStringScientific(fNumber, nCharCount, GetCurrentLanguageData(), rOutString);
2457 return true;
2460 sal_uInt16 nPrec = nCharCount >= nDigitPre ? nCharCount - nDigitPre : 0;
2461 if (nPrec && bSign)
2463 // Subtract the negative sign.
2464 --nPrec;
2466 if (nPrec)
2468 // Subtract the decimal point.
2469 --nPrec;
2471 ImpGetOutputStdToPrecision(fNumber, rOutString, nPrec, rNatNum);
2472 if (rOutString.getLength() > nCharCount)
2474 // String still wider than desired. Switch to scientific notation.
2475 lcl_GetOutputStringScientific(fNumber, nCharCount, GetCurrentLanguageData(), rOutString);
2477 return true;
2480 sal_uInt16 SvNumberformat::GetSubformatIndex (double fNumber ) const
2482 sal_uInt16 nIx; // Index of the partial format
2483 double fLimit_1 = fLimit1;
2484 short nCheck = ImpCheckCondition(fNumber, fLimit_1, eOp1);
2485 if (nCheck == -1 || nCheck == 1) // Only 1 String or True
2487 nIx = 0;
2489 else
2491 double fLimit_2 = fLimit2;
2492 nCheck = ImpCheckCondition(fNumber, fLimit_2, eOp2);
2493 if (nCheck == -1 || nCheck == 1)
2495 nIx = 1;
2497 else
2499 nIx = 2;
2502 return nIx;
2505 bool SvNumberformat::GetOutputString(double fNumber,
2506 OUString& OutString,
2507 const Color** ppColor,
2508 const NativeNumberWrapper& rNatNum,
2509 const SvNFLanguageData& rCurrentLang,
2510 bool bStarFlag) const
2512 bool bRes = false;
2513 OutString.clear();
2514 *ppColor = nullptr; // No color change
2515 if (eType & SvNumFormatType::LOGICAL && sFormatstring == rScan.GetKeywords()[NF_KEY_BOOLEAN])
2517 if (fNumber)
2519 OutString = rScan.GetTrueString();
2521 else
2523 OutString = rScan.GetFalseString();
2525 return false;
2527 OUStringBuffer sBuff(64);
2528 if (eType & SvNumFormatType::TEXT)
2530 ImpGetOutputStandard(fNumber, sBuff, rNatNum);
2531 OutString = sBuff.makeStringAndClear();
2532 return false;
2534 bool bHadStandard = false;
2535 if (bStandard) // Individual standard formats
2537 if (rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION) // All number format InputLine
2539 ImpGetOutputInputLine(fNumber, OutString);
2540 return false;
2542 switch (eType)
2544 case SvNumFormatType::NUMBER: // Standard number format
2545 if (rScan.GetStandardPrec() == SvNumberFormatter::UNLIMITED_PRECISION)
2547 if (std::signbit(fNumber))
2549 if (!(fNumber < 0.0))
2550 fNumber = -fNumber; // do not display -0.0
2552 if (fNumber == 0.0)
2554 OutString = "0";
2556 else if (fNumber < 1.0 && fNumber > -1.0)
2558 // Decide whether to display as 0.000000123... or 1.23...e-07
2559 bool bFix = (fNumber < -EXP_LOWER_BOUND || EXP_LOWER_BOUND < fNumber);
2560 if (!bFix)
2562 // Arbitrary, not too many 0s visually, start E2 at 1E-10.
2563 constexpr sal_Int32 kMaxExp = 9;
2564 const sal_Int32 nExp = static_cast<sal_Int32>(ceil( -log10( fabs( fNumber))));
2565 if (nExp <= kMaxExp && rtl::math::approxEqual(
2566 rtl::math::round( fNumber, 16), rtl::math::round( fNumber, nExp + 16)))
2568 // Not too many significant digits or accuracy
2569 // artefacts, otherwise leave everything to E2
2570 // format.
2571 bFix = true;
2574 if (bFix)
2575 OutString = ::rtl::math::doubleToUString( fNumber,
2576 rtl_math_StringFormat_F,
2577 rtl_math_DecimalPlaces_Max,
2578 GetCurrentLanguageData().GetNumDecimalSep()[0], true);
2579 else
2580 OutString = ::rtl::math::doubleToUString( fNumber,
2581 rtl_math_StringFormat_E2,
2582 rtl_math_DecimalPlaces_Max,
2583 GetCurrentLanguageData().GetNumDecimalSep()[0], true);
2585 else
2587 OutString = ::rtl::math::doubleToUString( fNumber,
2588 rtl_math_StringFormat_Automatic,
2589 rtl_math_DecimalPlaces_Max,
2590 GetCurrentLanguageData().GetNumDecimalSep()[0], true);
2592 return false;
2594 ImpGetOutputStandard(fNumber, sBuff, rNatNum);
2595 bHadStandard = true;
2596 break;
2597 case SvNumFormatType::DATE:
2598 bRes |= ImpGetDateOutput(fNumber, 0, bStarFlag, rNatNum, rCurrentLang, sBuff);
2599 bHadStandard = true;
2600 break;
2601 case SvNumFormatType::TIME:
2602 bRes |= ImpGetTimeOutput(fNumber, 0, bStarFlag, rNatNum, rCurrentLang, sBuff);
2603 bHadStandard = true;
2604 break;
2605 case SvNumFormatType::DATETIME:
2606 bRes |= ImpGetDateTimeOutput(fNumber, 0, bStarFlag, rNatNum, rCurrentLang, sBuff);
2607 bHadStandard = true;
2608 break;
2609 default: break;
2612 if ( !bHadStandard )
2614 sal_uInt16 nIx = GetSubformatIndex ( fNumber ); // Index of the partial format
2615 if (fNumber < 0.0 &&
2616 ((nIx == 0 && IsFirstSubformatRealNegative()) || // 1st, usually positive subformat
2617 (nIx == 1 && IsSecondSubformatRealNegative()))) // 2nd, usually negative subformat
2619 fNumber = -fNumber; // eliminate sign
2621 *ppColor = NumFor[nIx].GetColor();
2622 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
2623 const sal_uInt16 nCnt = NumFor[nIx].GetCount();
2624 if (nCnt == 0 && rInfo.eScannedType == SvNumFormatType::EMPTY)
2626 return false; // Empty => nothing
2628 else if (nCnt == 0) // Else Standard Format
2630 ImpGetOutputStandard(fNumber, sBuff, rNatNum);
2631 OutString = sBuff.makeStringAndClear();
2632 return false;
2634 switch (rInfo.eScannedType)
2636 case SvNumFormatType::TEXT:
2637 case SvNumFormatType::DEFINED:
2638 for (sal_uInt16 i = 0; i < nCnt; i++)
2640 switch (rInfo.nTypeArray[i])
2642 case NF_SYMBOLTYPE_STAR:
2643 if( bStarFlag )
2645 bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]);
2647 break;
2648 case NF_SYMBOLTYPE_BLANK:
2649 if (rInfo.sStrArray[i].getLength() >= 2)
2650 InsertBlanks(sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] );
2651 break;
2652 case NF_SYMBOLTYPE_STRING:
2653 case NF_SYMBOLTYPE_CURRENCY:
2654 sBuff.append(rInfo.sStrArray[i]);
2655 break;
2656 case NF_SYMBOLTYPE_THSEP:
2657 if (rInfo.nThousand == 0)
2659 sBuff.append(rInfo.sStrArray[i]);
2661 break;
2662 default:
2663 break;
2666 break;
2667 case SvNumFormatType::DATE:
2668 bRes |= ImpGetDateOutput(fNumber, nIx, bStarFlag, rNatNum, rCurrentLang, sBuff);
2669 break;
2670 case SvNumFormatType::TIME:
2671 bRes |= ImpGetTimeOutput(fNumber, nIx, bStarFlag, rNatNum, rCurrentLang, sBuff);
2672 break;
2673 case SvNumFormatType::DATETIME:
2674 bRes |= ImpGetDateTimeOutput(fNumber, nIx, bStarFlag, rNatNum, rCurrentLang, sBuff);
2675 break;
2676 case SvNumFormatType::NUMBER:
2677 case SvNumFormatType::PERCENT:
2678 case SvNumFormatType::CURRENCY:
2679 bRes |= ImpGetNumberOutput(fNumber, nIx, bStarFlag, rNatNum, sBuff);
2680 break;
2681 case SvNumFormatType::LOGICAL:
2682 bRes |= ImpGetLogicalOutput(fNumber, nIx, rNatNum, sBuff);
2683 break;
2684 case SvNumFormatType::FRACTION:
2685 bRes |= ImpGetFractionOutput(fNumber, nIx, bStarFlag, rNatNum, sBuff);
2686 break;
2687 case SvNumFormatType::SCIENTIFIC:
2688 bRes |= ImpGetScientificOutput(fNumber, nIx, bStarFlag, rNatNum, sBuff);
2689 break;
2690 default: break;
2693 OutString = sBuff.makeStringAndClear();
2694 return bRes;
2697 bool SvNumberformat::ImpGetScientificOutput(double fNumber,
2698 sal_uInt16 nIx,
2699 bool bStarFlag,
2700 const NativeNumberWrapper& rNatNum,
2701 OUStringBuffer& sStr) const
2703 bool bRes = false;
2704 bool bSign = false;
2706 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
2707 const sal_uInt16 nCnt = NumFor[nIx].GetCount();
2709 if (fNumber < 0)
2711 if (nIx == 0) // Not in the ones at the end
2713 bSign = true; // Formats
2715 fNumber = -fNumber;
2718 sStr = ::rtl::math::doubleToUString( fNumber,
2719 rtl_math_StringFormat_E,
2720 rInfo.nCntPre + rInfo.nCntPost - 1, '.' );
2721 OUStringBuffer ExpStr;
2722 short nExpSign = 1;
2723 sal_Int32 nExPos = sStr.indexOf('E');
2724 sal_Int32 nDecPos = -1;
2726 if ( nExPos >= 0 )
2728 // split into mantissa and exponent and get rid of "E+" or "E-"
2729 sal_Int32 nExpStart = nExPos + 1;
2731 switch ( sStr[ nExpStart ] )
2733 case '-' :
2734 nExpSign = -1;
2735 [[fallthrough]];
2736 case '+' :
2737 ++nExpStart;
2738 break;
2740 ExpStr = sStr.subView( nExpStart ); // part following the "E+"
2741 sStr.truncate( nExPos );
2743 if ( rInfo.nCntPre != 1 ) // rescale Exp
2745 sal_Int32 nExp = OUString::unacquired(ExpStr).toInt32() * nExpSign;
2746 sal_Int32 nRescale = (rInfo.nCntPre != 0) ? nExp % static_cast<sal_Int32>(rInfo.nCntPre) : -1;
2747 if( nRescale < 0 && rInfo.nCntPre != 0 )
2748 nRescale += static_cast<sal_Int32>(rInfo.nCntPre);
2749 nExp -= nRescale;
2750 if ( nExp < 0 )
2752 nExpSign = -1;
2753 nExp = -nExp;
2755 else
2757 nExpSign = 1;
2759 ExpStr = OUString::number( nExp );
2760 const sal_Unicode cFirstDigit = sStr[0];
2761 // rescale mantissa
2762 sStr = ::rtl::math::doubleToUString( fNumber,
2763 rtl_math_StringFormat_E,
2764 nRescale + rInfo.nCntPost, '.' );
2766 // sStr now may contain a rounded-up value shifted into the next
2767 // magnitude, for example 1.000E+02 (4 digits) for fNumber 99.995
2768 // (9.9995E+02 rounded to 3 decimals) but we want the final result
2769 // to be 100.00E+00 (5 digits), so for the following fill routines
2770 // below to work correctly append a zero decimal.
2771 /* TODO: this is awkward, could an engineering notation mode be
2772 * introduced to rtl_math_doubleToUString()? */
2773 sStr.truncate( sStr.indexOf('E') );
2774 if (sStr[0] == '1' && cFirstDigit != '1')
2775 sStr.append('0');
2778 // cut any decimal delimiter
2779 sal_Int32 index = 0;
2781 while((index = sStr.indexOf('.', index)) >= 0)
2783 if (nDecPos < 0)
2784 nDecPos = index;
2785 sStr.remove(index, 1);
2789 sal_uInt16 j = nCnt-1; // Last symbol
2790 sal_Int32 k = ExpStr.getLength() - 1; // Position in ExpStr
2791 sal_Int32 nZeros = 0; // Erase leading zeros
2793 // erase all leading zeros except last one
2794 while (nZeros < k && ExpStr[nZeros] == '0')
2796 ++nZeros;
2798 if (nZeros)
2800 ExpStr.remove( 0, nZeros);
2803 // restore leading zeros or blanks according to format '0' or '?' tdf#156449
2804 bRes |= ImpNumberFill(rNatNum, ExpStr, fNumber, k, j, nIx, NF_SYMBOLTYPE_EXP, bStarFlag);
2806 bool bCont = true;
2808 if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_EXP)
2810 const OUString& rStr = rInfo.sStrArray[j];
2811 if (nExpSign == -1)
2813 ExpStr.insert(0, '-');
2815 else if (rStr.getLength() > 1 && rStr[1] == '+')
2817 ExpStr.insert(0, '+');
2819 ExpStr.insert(0, rStr[0]);
2820 if ( j )
2822 j--;
2824 else
2826 bCont = false;
2829 // Continue main number:
2830 if ( !bCont )
2832 sStr.truncate();
2834 else
2836 bRes |= ImpDecimalFill(rNatNum, sStr, fNumber, nDecPos, j, nIx, false, bStarFlag);
2839 if (bSign)
2841 sStr.insert(0, '-');
2843 sStr.append(ExpStr);
2845 return bRes;
2848 double SvNumberformat::GetRoundFractionValue ( double fNumber ) const
2850 sal_uInt16 nIx = GetSubformatIndex ( fNumber );
2851 double fIntPart = 0.0; // integer part of fraction
2852 sal_Int64 nFrac = 0, nDiv = 1; // numerator and denominator
2853 double fSign = (fNumber < 0.0) ? -1.0 : 1.0;
2854 // fNumber is modified in ImpGetFractionElements to absolute fractional part
2855 ImpGetFractionElements ( fNumber, nIx, fIntPart, nFrac, nDiv );
2856 if ( nDiv > 0 )
2857 return fSign * ( fIntPart + static_cast<double>(nFrac) / static_cast<double>(nDiv) );
2858 else
2859 return fSign * fIntPart;
2862 void SvNumberformat::ImpGetFractionElements ( double& fNumber, sal_uInt16 nIx,
2863 double& fIntPart, sal_Int64& nFrac, sal_Int64& nDiv ) const
2865 if ( fNumber < 0.0 )
2866 fNumber = -fNumber;
2867 fIntPart = floor(fNumber); // Integral part
2868 fNumber -= fIntPart; // Fractional part
2869 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
2870 sal_Int64 nForcedDiv = lcl_GetDenominatorString( rInfo, NumFor[nIx].GetCount() ).toInt32();
2871 if( nForcedDiv > 0 )
2872 { // Forced Denominator
2873 nDiv = nForcedDiv;
2874 nFrac = static_cast<sal_Int64>(floor ( fNumber * nDiv ));
2875 double fFracNew = static_cast<double>(nFrac) / static_cast<double>(nDiv);
2876 double fFracNew1 = static_cast<double>(nFrac + 1) / static_cast<double>(nDiv);
2877 double fDiff = fNumber - fFracNew;
2878 if( fDiff > ( fFracNew1 - fNumber ) )
2880 nFrac++;
2883 else // Calculated Denominator
2885 nDiv = 1;
2886 sal_Int64 nBasis = static_cast<sal_Int64>(floor( pow(10.0,rInfo.nCntExp))) - 1; // 9, 99, 999 ,...
2887 sal_Int64 nFracPrev = 1, nDivPrev = 0, nFracNext, nDivNext, nPartialDenom;
2888 double fRemainder = fNumber;
2890 // Use continued fraction representation of fNumber
2891 // See https://en.wikipedia.org/wiki/Continued_fraction#Best_rational_approximations
2892 while ( fRemainder > 0.0 )
2894 double fTemp = 1.0 / fRemainder; // 64bits precision required when fRemainder is very weak
2895 nPartialDenom = static_cast<sal_Int64>(floor(fTemp)); // due to floating point notation with double precision
2896 fRemainder = fTemp - static_cast<double>(nPartialDenom);
2897 nDivNext = nPartialDenom * nDiv + nDivPrev;
2898 if ( nDivNext <= nBasis ) // continue loop
2900 nFracNext = nPartialDenom * nFrac + nFracPrev;
2901 nFracPrev = nFrac;
2902 nFrac = nFracNext;
2903 nDivPrev = nDiv;
2904 nDiv = nDivNext;
2906 else // calculate collateral fraction and exit
2908 sal_Int64 nCollat = (nBasis - nDivPrev) / nDiv;
2909 if ( 2 * nCollat >= nPartialDenom )
2911 sal_Int64 nFracTest = nCollat * nFrac + nFracPrev;
2912 sal_Int64 nDivTest = nCollat * nDiv + nDivPrev;
2913 double fSign = (static_cast<double>(nFrac) > fNumber * static_cast<double>(nDiv))?1.0:-1.0;
2914 if ( fSign * ( double(nFrac * nDivTest + nDiv * nFracTest) - 2.0 * double(nDiv * nDivTest) * fNumber ) > 0.0 )
2916 nFrac = nFracTest;
2917 nDiv = nDivTest;
2920 fRemainder = 0.0; // exit while loop
2924 if (nFrac >= nDiv)
2926 ++fIntPart;
2927 nFrac = 0;
2928 nDiv = ( nForcedDiv > 0 ) ? nForcedDiv : 1;
2932 bool SvNumberformat::ImpGetFractionOutput(double fNumber,
2933 sal_uInt16 nIx,
2934 bool bStarFlag,
2935 const NativeNumberWrapper& rNatNum,
2936 OUStringBuffer& sBuff) const
2938 bool bRes = false;
2939 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
2940 const sal_uInt16 nCnt = NumFor[nIx].GetCount();
2941 OUStringBuffer sStr, sFrac, sDiv; // Strings, value for Integral part Numerator and denominator
2942 bool bSign = ( (fNumber < 0) && (nIx == 0) ); // sign Not in the ones at the end
2943 const OUString sIntegerFormat = lcl_GetFractionIntegerString(rInfo, nCnt);
2944 const OUString sNumeratorFormat = lcl_GetNumeratorString(rInfo, nCnt);
2945 const OUString sDenominatorFormat = lcl_GetDenominatorString(rInfo, nCnt);
2947 sal_Int64 nFrac = 0, nDiv = 1;
2948 double fNum = floor(fNumber); // Integral part
2950 if (fNum > D_MAX_U_INT32 || rInfo.nCntExp > 9) // Too large
2952 sBuff = ImpSvNumberformatScan::sErrStr;
2953 return false;
2955 if (rInfo.nCntExp == 0)
2957 SAL_WARN( "svl.numbers", "SvNumberformat:: Fraction, nCntExp == 0");
2958 sBuff.truncate();
2959 return false;
2962 ImpGetFractionElements( fNumber, nIx, fNum, nFrac, nDiv);
2964 if (rInfo.nCntPre == 0) // Improper fraction
2966 double fNum1 = fNum * static_cast<double>(nDiv) + static_cast<double>(nFrac);
2968 if (fNum1 > D_MAX_INTEGER)
2970 sBuff = ImpSvNumberformatScan::sErrStr;
2971 return false;
2973 nFrac = static_cast<sal_Int64>(floor(fNum1));
2975 else if (fNum == 0.0 && nFrac != 0)
2978 else
2980 char aBuf[100];
2981 o3tl::sprintf( aBuf, "%.f", fNum ); // simple rounded integer
2982 sStr.appendAscii( aBuf );
2983 ::impTransliterate(sStr, NumFor[nIx].GetNatNum(), rNatNum);
2985 bool bHideFraction = (rInfo.nCntPre > 0 && nFrac == 0
2986 && (sNumeratorFormat.indexOf('0') < 0)
2987 && (sDenominatorFormat.indexOf('0') < 0
2988 || sDenominatorFormat.toInt32() > 0) );
2989 if ( bHideFraction )
2991 sDiv.truncate();
2993 else // if there are some '0' in format, force display of fraction
2995 sFrac = ImpIntToString(rNatNum, nIx, nFrac);
2996 sDiv = ImpIntToString(rNatNum, nIx, nDiv);
2999 sal_uInt16 j = nCnt-1; // Last symbol -> backwards
3000 sal_Int32 k; // Denominator
3002 bRes |= ImpNumberFill(rNatNum, sDiv, fNumber, k, j, nIx, NF_SYMBOLTYPE_FRAC, bStarFlag, true);
3004 bool bCont = true;
3005 if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_FRAC)
3007 if ( bHideFraction )
3008 { // do not insert blank for fraction if there is no '?'
3009 if ( sNumeratorFormat.indexOf('?') >= 0
3010 || sDenominatorFormat.indexOf('?') >= 0 )
3011 sDiv.insert(0, ' ');
3013 else
3015 sDiv.insert(0, rInfo.sStrArray[j][0]);
3017 if ( j )
3019 j--;
3021 else
3023 bCont = false;
3026 // Further numerators:
3027 if ( !bCont )
3029 sFrac.truncate();
3031 else
3033 bRes |= ImpNumberFill(rNatNum, sFrac, fNumber, k, j, nIx, NF_SYMBOLTYPE_FRACBLANK, bStarFlag);
3034 bCont = false; // there is no integer part?
3035 if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_FRACBLANK)
3037 if ( j )
3039 if ( bHideFraction )
3040 { // '?' in any format force display of blank as delimiter
3041 if ( sIntegerFormat.indexOf('?') >= 0
3042 || sNumeratorFormat.indexOf('?') >= 0
3043 || sDenominatorFormat.indexOf('?') >= 0 )
3045 for (sal_Int32 i = 0; i < rInfo.sStrArray[j].getLength(); i++)
3046 sFrac.insert(0, ' ');
3049 else
3051 if ( fNum != 0.0 || sIntegerFormat.indexOf('0') >= 0 )
3052 sFrac.insert(0, rInfo.sStrArray[j]); // insert Blank string only if there are both integer and fraction
3053 else
3055 if ( sIntegerFormat.indexOf('?') >= 0
3056 || sNumeratorFormat.indexOf('?') >= 0 )
3058 for (sal_Int32 i = 0; i < rInfo.sStrArray[j].getLength(); i++)
3059 sFrac.insert(0, ' ');
3063 j--;
3064 bCont = true; // Yes, there is an integer
3066 else
3067 sFrac.insert(0, rInfo.sStrArray[j]);
3070 // Continue integer part
3071 if ( !bCont )
3073 sStr.truncate();
3075 else
3077 k = sStr.getLength(); // After last figure
3078 bRes |= ImpNumberFillWithThousands(rNatNum, sStr, fNumber, k, j, nIx,
3079 rInfo.nCntPre, bStarFlag);
3081 if (bSign && (nFrac != 0 || fNum != 0.0))
3083 sBuff.insert(0, '-'); // Not -0
3085 sBuff.append(sStr);
3086 sBuff.append(sFrac);
3087 sBuff.append(sDiv);
3088 return bRes;
3091 sal_uInt16 SvNumberformat::ImpGetFractionOfSecondString( OUStringBuffer& rBuf, double fFractionOfSecond,
3092 int nFractionDecimals, bool bAddOneRoundingDecimal, sal_uInt16 nIx, sal_uInt16 nMinimumInputLineDecimals,
3093 const NativeNumberWrapper& rNatNum) const
3095 if (!nFractionDecimals)
3096 return 0;
3098 // nFractionDecimals+1 to not round up what Time::GetClock() carefully
3099 // truncated.
3100 rBuf.append( rtl::math::doubleToUString( fFractionOfSecond, rtl_math_StringFormat_F,
3101 (bAddOneRoundingDecimal ? nFractionDecimals + 1 : nFractionDecimals), '.'));
3102 rBuf.stripStart('0');
3103 rBuf.stripStart('.');
3104 if (bAddOneRoundingDecimal && rBuf.getLength() > nFractionDecimals)
3105 rBuf.truncate( nFractionDecimals); // the digit appended because of nFractionDecimals+1
3106 if (nMinimumInputLineDecimals)
3108 rBuf.stripEnd('0');
3109 for (sal_Int32 index = rBuf.getLength(); index < nMinimumInputLineDecimals; ++index)
3111 rBuf.append('0');
3113 ::impTransliterate(rBuf, NumFor[nIx].GetNatNum(), rNatNum);
3114 nFractionDecimals = rBuf.getLength();
3116 else
3118 ::impTransliterate(rBuf, NumFor[nIx].GetNatNum(), rNatNum);
3120 return static_cast<sal_uInt16>(nFractionDecimals);
3123 bool SvNumberformat::ImpGetTimeOutput(double fNumber,
3124 sal_uInt16 nIx,
3125 bool bStarFlag,
3126 const NativeNumberWrapper& rNatNum,
3127 const SvNFLanguageData& rCurrentLang,
3128 OUStringBuffer& sBuff) const
3130 using namespace ::com::sun::star::i18n;
3131 bool bCalendarSet = false;
3132 const double fNumberOrig = fNumber;
3133 bool bRes = false;
3134 bool bSign = false;
3135 if (fNumber < 0.0)
3137 fNumber = -fNumber;
3138 if (nIx == 0)
3140 bSign = true;
3143 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
3144 bool bInputLine;
3145 sal_Int32 nCntPost;
3146 if ( rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION &&
3147 0 < rInfo.nCntPost && rInfo.nCntPost < kTimeSignificantRound )
3149 bInputLine = true;
3150 nCntPost = kTimeSignificantRound;
3152 else
3154 bInputLine = false;
3155 nCntPost = rInfo.nCntPost;
3158 OUStringBuffer sSecStr;
3159 sal_Int32 nSecPos = 0; // For figure by figure processing
3160 sal_uInt32 nHour, nMin, nSec;
3161 if (!rInfo.bThousand) // No [] format
3163 sal_uInt16 nCHour, nCMinute, nCSecond;
3164 double fFractionOfSecond;
3165 tools::Time::GetClock( fNumberOrig, nCHour, nCMinute, nCSecond, fFractionOfSecond, nCntPost);
3166 nHour = nCHour;
3167 nMin = nCMinute;
3168 nSec = nCSecond;
3169 nCntPost = ImpGetFractionOfSecondString( sSecStr, fFractionOfSecond, nCntPost, true, nIx,
3170 (bInputLine ? rInfo.nCntPost : 0), rNatNum );
3172 else
3174 const double fTime = rtl::math::round( fNumber * 86400.0, int(nCntPost));
3175 if (bSign && fTime == 0.0)
3177 bSign = false; // Not -00:00:00
3179 if (fTime > D_MAX_U_INT32)
3181 sBuff = ImpSvNumberformatScan::sErrStr;
3182 return false;
3184 sal_uInt32 nSeconds = static_cast<sal_uInt32>(fTime);
3186 nCntPost = ImpGetFractionOfSecondString( sSecStr, fTime - nSeconds, nCntPost, false, nIx,
3187 (bInputLine ? rInfo.nCntPost : 0), rNatNum );
3189 if (rInfo.nThousand == 3) // [ss]
3191 nHour = 0;
3192 nMin = 0;
3193 nSec = nSeconds;
3195 else if (rInfo.nThousand == 2) // [mm]:ss
3197 nHour = 0;
3198 nMin = nSeconds / 60;
3199 nSec = nSeconds % 60;
3201 else if (rInfo.nThousand == 1) // [hh]:mm:ss
3203 nHour = nSeconds / 3600;
3204 nMin = (nSeconds%3600) / 60;
3205 nSec = nSeconds%60;
3207 else
3209 // TODO What should these be set to?
3210 nHour = 0;
3211 nMin = 0;
3212 nSec = 0;
3216 sal_Unicode cAmPm = ' '; // a or p
3217 if (rInfo.nCntExp) // AM/PM
3219 if (nHour == 0)
3221 nHour = 12;
3222 cAmPm = 'a';
3224 else if (nHour < 12)
3226 cAmPm = 'a';
3228 else
3230 cAmPm = 'p';
3231 if (nHour > 12)
3233 nHour -= 12;
3237 const sal_uInt16 nCnt = NumFor[nIx].GetCount();
3238 for (sal_uInt16 i = 0; i < nCnt; i++)
3240 sal_Int32 nLen;
3241 switch (rInfo.nTypeArray[i])
3243 case NF_SYMBOLTYPE_STAR:
3244 if( bStarFlag )
3246 bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]);
3248 break;
3249 case NF_SYMBOLTYPE_BLANK:
3250 if (rInfo.sStrArray[i].getLength() >= 2)
3251 InsertBlanks(sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] );
3252 break;
3253 case NF_SYMBOLTYPE_STRING:
3254 case NF_SYMBOLTYPE_CURRENCY:
3255 case NF_SYMBOLTYPE_DATESEP:
3256 case NF_SYMBOLTYPE_TIMESEP:
3257 case NF_SYMBOLTYPE_TIME100SECSEP:
3258 sBuff.append(rInfo.sStrArray[i]);
3259 break;
3260 case NF_SYMBOLTYPE_DIGIT:
3261 nLen = ( bInputLine && i > 0 &&
3262 (rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING ||
3263 rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_TIME100SECSEP) ?
3264 nCntPost : rInfo.sStrArray[i].getLength() );
3265 for (sal_Int32 j = 0; j < nLen && nSecPos < nCntPost && nSecPos < sSecStr.getLength(); ++j)
3267 sBuff.append(sSecStr[nSecPos]);
3268 nSecPos++;
3270 break;
3271 case NF_KEY_AMPM: // AM/PM
3273 CalendarWrapper& rCal = *rCurrentLang.GetCalendar();
3274 if ( !bCalendarSet )
3276 double fDiff = DateTime::Sub( DateTime(rScan.GetNullDate()), rCal.getEpochStart());
3277 fDiff += fNumberOrig;
3278 rCal.setLocalDateTime( fDiff );
3279 bCalendarSet = true;
3281 if (cAmPm == 'a')
3283 sBuff.append(rCal.getDisplayName(
3284 CalendarDisplayIndex::AM_PM, AmPmValue::AM, 0 ));
3286 else
3288 sBuff.append(rCal.getDisplayName(
3289 CalendarDisplayIndex::AM_PM, AmPmValue::PM, 0 ));
3291 break;
3293 case NF_KEY_AP: // A/P
3294 if (cAmPm == 'a')
3296 sBuff.append('a');
3298 else
3300 sBuff.append('p');
3302 break;
3303 case NF_KEY_MI: // M
3304 sBuff.append(ImpIntToString(rNatNum, nIx, nMin ));
3305 break;
3306 case NF_KEY_MMI: // MM
3307 sBuff.append(ImpIntToString(rNatNum, nIx, nMin, 2 ));
3308 break;
3309 case NF_KEY_H: // H
3310 sBuff.append(ImpIntToString(rNatNum, nIx, nHour ));
3311 break;
3312 case NF_KEY_HH: // HH
3313 sBuff.append(ImpIntToString(rNatNum, nIx, nHour, 2 ));
3314 break;
3315 case NF_KEY_S: // S
3316 sBuff.append(ImpIntToString(rNatNum, nIx, nSec ));
3317 break;
3318 case NF_KEY_SS: // SS
3319 sBuff.append(ImpIntToString(rNatNum, nIx, nSec, 2 ));
3320 break;
3321 default:
3322 break;
3325 if (bSign && rInfo.bThousand)
3327 sBuff.insert(0, '-');
3329 return bRes;
3333 /** If a day of month occurs within the format, the month name is in possessive
3334 genitive case if the day follows the month, and partitive case if the day
3335 precedes the month. If there is no day of month the nominative case (noun)
3336 is returned. Also if the month is immediately preceded or followed by a
3337 literal string other than space and not followed by a comma, the nominative
3338 name is used, this prevents duplicated casing for MMMM\t\a and such in
3339 documents imported from (e.g. Finnish) Excel or older LibO/OOo releases.
3342 // IDEA: instead of eCodeType pass the index to nTypeArray and restrict
3343 // inspection of month name around that one, that would enable different month
3344 // cases in one format. Though probably the most rare use case ever...
3346 sal_Int32 SvNumberformat::ImpUseMonthCase( int & io_nState, const ImpSvNumFor& rNumFor, NfKeywordIndex eCodeType )
3348 using namespace ::com::sun::star::i18n;
3349 if (!io_nState)
3351 bool bMonthSeen = false;
3352 bool bDaySeen = false;
3353 const ImpSvNumberformatInfo& rInfo = rNumFor.Info();
3354 const sal_uInt16 nCount = rNumFor.GetCount();
3355 for (sal_uInt16 i = 0; i < nCount && io_nState == 0; ++i)
3357 sal_Int32 nLen;
3358 switch (rInfo.nTypeArray[i])
3360 case NF_KEY_D :
3361 case NF_KEY_DD :
3362 if (bMonthSeen)
3364 io_nState = 2;
3366 else
3368 bDaySeen = true;
3370 break;
3371 case NF_KEY_MMM:
3372 case NF_KEY_MMMM:
3373 case NF_KEY_MMMMM:
3374 if ((i < nCount-1 &&
3375 rInfo.nTypeArray[i+1] == NF_SYMBOLTYPE_STRING &&
3376 // Literal following, not empty, space nor comma.
3377 !rInfo.sStrArray[i+1].isEmpty() &&
3378 rInfo.sStrArray[i+1][0] != ' ' && rInfo.sStrArray[i+1][0] != ',') ||
3379 (i > 0 && rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING &&
3380 ((nLen = rInfo.sStrArray[i-1].getLength()) > 0) &&
3381 // Literal preceding, not space.
3382 rInfo.sStrArray[i-1][nLen-1] != ' '))
3384 io_nState = 1;
3386 else if (bDaySeen)
3388 io_nState = 3;
3390 else
3392 bMonthSeen = true;
3394 break;
3397 if (io_nState == 0)
3399 io_nState = 1; // No day of month
3402 switch (io_nState)
3404 case 1:
3405 // No day of month or forced nominative
3406 switch (eCodeType)
3408 case NF_KEY_MMM:
3409 return CalendarDisplayCode::SHORT_MONTH_NAME;
3410 case NF_KEY_MMMM:
3411 return CalendarDisplayCode::LONG_MONTH_NAME;
3412 case NF_KEY_MMMMM:
3413 return CalendarDisplayCode::NARROW_MONTH_NAME;
3414 default:
3415 ; // nothing
3417 break;
3418 case 2:
3419 // Day of month follows month (the month's 17th)
3420 switch (eCodeType)
3422 case NF_KEY_MMM:
3423 return CalendarDisplayCode::SHORT_GENITIVE_MONTH_NAME;
3424 case NF_KEY_MMMM:
3425 return CalendarDisplayCode::LONG_GENITIVE_MONTH_NAME;
3426 case NF_KEY_MMMMM:
3427 return CalendarDisplayCode::NARROW_GENITIVE_MONTH_NAME;
3428 default:
3429 ; // Nothing
3431 break;
3432 case 3:
3433 // Day of month precedes month (17 of month)
3434 switch (eCodeType)
3436 case NF_KEY_MMM:
3437 return CalendarDisplayCode::SHORT_PARTITIVE_MONTH_NAME;
3438 case NF_KEY_MMMM:
3439 return CalendarDisplayCode::LONG_PARTITIVE_MONTH_NAME;
3440 case NF_KEY_MMMMM:
3441 return CalendarDisplayCode::NARROW_PARTITIVE_MONTH_NAME;
3442 default:
3443 ; // nothing
3445 break;
3447 SAL_WARN( "svl.numbers", "ImpUseMonthCase: unhandled keyword index eCodeType");
3448 return CalendarDisplayCode::LONG_MONTH_NAME;
3451 namespace {
3453 bool ImpIsOtherCalendar( const ImpSvNumFor& rNumFor, const CalendarWrapper& rCal )
3455 if ( rCal.getUniqueID() != GREGORIAN )
3457 return false;
3459 const ImpSvNumberformatInfo& rInfo = rNumFor.Info();
3460 const sal_uInt16 nCnt = rNumFor.GetCount();
3461 sal_uInt16 i;
3462 for ( i = 0; i < nCnt; i++ )
3464 switch ( rInfo.nTypeArray[i] )
3466 case NF_SYMBOLTYPE_CALENDAR :
3467 return false;
3468 case NF_KEY_EC :
3469 case NF_KEY_EEC :
3470 case NF_KEY_R :
3471 case NF_KEY_RR :
3472 case NF_KEY_AAA :
3473 case NF_KEY_AAAA :
3474 case NF_KEY_G :
3475 case NF_KEY_GG :
3476 case NF_KEY_GGG :
3477 return true;
3480 return false;
3485 void SvNumberformat::SwitchToOtherCalendar( OUString& rOrgCalendar,
3486 double& fOrgDateTime,
3487 CalendarWrapper& rCal ) const
3489 if ( rCal.getUniqueID() != GREGORIAN )
3490 return;
3492 using namespace ::com::sun::star::i18n;
3493 const css::uno::Sequence< OUString > xCals = rCal.getAllCalendars(
3494 rLoc().getLanguageTag().getLocale() );
3495 sal_Int32 nCnt = xCals.getLength();
3496 if ( nCnt <= 1 )
3497 return;
3499 auto pCal = std::find_if(xCals.begin(), xCals.end(),
3500 [](const OUString& rCalName) { return rCalName != GREGORIAN; });
3501 if (pCal == xCals.end())
3502 return;
3504 if ( !rOrgCalendar.getLength() )
3506 rOrgCalendar = rCal.getUniqueID();
3507 fOrgDateTime = rCal.getDateTime();
3509 rCal.loadCalendar( *pCal, rLoc().getLanguageTag().getLocale() );
3510 rCal.setDateTime( fOrgDateTime );
3513 void SvNumberformat::SwitchToGregorianCalendar( std::u16string_view rOrgCalendar,
3514 double fOrgDateTime,
3515 CalendarWrapper& rCal ) const
3517 if ( rOrgCalendar.size() && rCal.getUniqueID() != GREGORIAN )
3519 rCal.loadCalendar( GREGORIAN, rLoc().getLanguageTag().getLocale() );
3520 rCal.setDateTime( fOrgDateTime );
3524 bool SvNumberformat::ImpFallBackToGregorianCalendar( OUString& rOrgCalendar,
3525 double& fOrgDateTime,
3526 CalendarWrapper& rCal ) const
3528 using namespace ::com::sun::star::i18n;
3529 if ( rCal.getUniqueID() != GREGORIAN )
3531 sal_Int16 nVal = rCal.getValue( CalendarFieldIndex::ERA );
3532 if ( nVal == 0 && rCal.getLoadedCalendar().Eras[0].ID == "Dummy" )
3534 if ( !rOrgCalendar.getLength() )
3536 rOrgCalendar = rCal.getUniqueID();
3537 fOrgDateTime = rCal.getDateTime();
3539 else if ( rOrgCalendar == GREGORIAN )
3541 rOrgCalendar.clear();
3543 rCal.loadCalendar( GREGORIAN, rLoc().getLanguageTag().getLocale() );
3544 rCal.setDateTime( fOrgDateTime );
3545 return true;
3548 return false;
3552 #ifdef THE_FUTURE
3553 /* XXX NOTE: even if the ImpSwitchToSpecifiedCalendar method is currently
3554 * unused please don't remove it, it would be needed by
3555 * SwitchToSpecifiedCalendar(), see comment in
3556 * ImpSvNumberInputScan::GetDateRef() */
3558 bool SvNumberformat::ImpSwitchToSpecifiedCalendar( OUString& rOrgCalendar,
3559 double& fOrgDateTime,
3560 const ImpSvNumFor& rNumFor ) const
3562 const ImpSvNumberformatInfo& rInfo = rNumFor.Info();
3563 const sal_uInt16 nCnt = rNumFor.GetCount();
3564 for ( sal_uInt16 i = 0; i < nCnt; i++ )
3566 if ( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_CALENDAR )
3568 CalendarWrapper& rCal = GetCal();
3569 if ( !rOrgCalendar.getLength() )
3571 rOrgCalendar = rCal.getUniqueID();
3572 fOrgDateTime = rCal.getDateTime();
3574 rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLocale() );
3575 rCal.setDateTime( fOrgDateTime );
3576 return true;
3579 return false;
3581 #endif
3583 // static
3584 void SvNumberformat::ImpAppendEraG( OUStringBuffer& OutString,
3585 const CalendarWrapper& rCal,
3586 sal_Int16 nNatNum )
3588 using namespace ::com::sun::star::i18n;
3589 if ( rCal.getUniqueID() == "gengou" )
3591 sal_Unicode cEra;
3592 sal_Int16 nVal = rCal.getValue( CalendarFieldIndex::ERA );
3593 switch ( nVal )
3595 case 1:
3596 cEra = 'M';
3597 break;
3598 case 2:
3599 cEra = 'T';
3600 break;
3601 case 3:
3602 cEra = 'S';
3603 break;
3604 case 4:
3605 cEra = 'H';
3606 break;
3607 case 5:
3608 cEra = 'R';
3609 break;
3610 default:
3611 cEra = '?';
3612 break;
3614 OutString.append(cEra);
3616 else
3618 OutString.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum ));
3622 bool SvNumberformat::ImpIsIso8601( const ImpSvNumFor& rNumFor ) const
3624 bool bIsIso = false;
3625 if (eType & SvNumFormatType::DATE)
3627 enum State
3629 eNone,
3630 eAtYear,
3631 eAtSep1,
3632 eAtMonth,
3633 eAtSep2,
3634 eNotIso
3636 State eState = eNone;
3637 auto & rTypeArray = rNumFor.Info().nTypeArray;
3638 sal_uInt16 nCnt = rNumFor.GetCount();
3639 for (sal_uInt16 i=0; i < nCnt && !bIsIso && eState != eNotIso; ++i)
3641 switch ( rTypeArray[i] )
3643 case NF_KEY_YY: // two digits not strictly ISO 8601
3644 case NF_KEY_YYYY:
3645 if (eState != eNone)
3647 eState = eNotIso;
3649 else
3651 eState = eAtYear;
3653 break;
3654 case NF_KEY_M: // single digit not strictly ISO 8601
3655 case NF_KEY_MM:
3656 if (eState != eAtSep1)
3658 eState = eNotIso;
3660 else
3662 eState = eAtMonth;
3664 break;
3665 case NF_KEY_D: // single digit not strictly ISO 8601
3666 case NF_KEY_DD:
3667 if (eState != eAtSep2)
3669 eState = eNotIso;
3671 else
3673 bIsIso = true;
3675 break;
3676 case NF_SYMBOLTYPE_STRING:
3677 case NF_SYMBOLTYPE_DATESEP:
3678 if (rNumFor.Info().sStrArray[i] == "-")
3680 if (eState == eAtYear)
3682 eState = eAtSep1;
3684 else if (eState == eAtMonth)
3686 eState = eAtSep2;
3688 else
3690 eState = eNotIso;
3693 else
3695 eState = eNotIso;
3697 break;
3698 default:
3699 eState = eNotIso;
3703 else
3705 SAL_WARN( "svl.numbers", "SvNumberformat::ImpIsIso8601: no date" );
3707 return bIsIso;
3710 static bool lcl_hasEra( const ImpSvNumFor& rNumFor )
3712 const ImpSvNumberformatInfo& rInfo = rNumFor.Info();
3713 const sal_uInt16 nCnt = rNumFor.GetCount();
3714 for ( sal_uInt16 i = 0; i < nCnt; i++ )
3716 switch ( rInfo.nTypeArray[i] )
3718 case NF_KEY_RR :
3719 case NF_KEY_G :
3720 case NF_KEY_GG :
3721 case NF_KEY_GGG :
3722 return true;
3725 return false;
3728 static bool lcl_isSignedYear( const CalendarWrapper& rCal, const ImpSvNumFor& rNumFor )
3730 return rCal.getValue( css::i18n::CalendarFieldIndex::ERA ) == 0 &&
3731 rCal.getUniqueID() == GREGORIAN && !lcl_hasEra( rNumFor );
3734 /* XXX: if needed this could be stripped from rEpochStart and diff adding and
3735 * moved to tools' DateTime to be reused elsewhere. */
3736 static bool lcl_getValidDate( const DateTime& rNullDate, const DateTime& rEpochStart, double& fNumber )
3738 static const DateTime aCE( Date(1,1,1));
3739 static const DateTime aMin( Date(1,1, SAL_MIN_INT16));
3740 static const DateTime aMax( Date(31,12, SAL_MAX_INT16), tools::Time(23,59,59, tools::Time::nanoSecPerSec - 1));
3741 static const double fMin = DateTime::Sub( aMin, aCE);
3742 static const double fMax = DateTime::Sub( aMax, aCE);
3743 // Value must be representable in our tools::Date proleptic Gregorian
3744 // calendar as well.
3745 const double fOff = DateTime::Sub( rNullDate, aCE) + fNumber;
3746 // Add diff between epochs to serial date number.
3747 const double fDiff = DateTime::Sub( rNullDate, rEpochStart);
3748 fNumber += fDiff;
3749 return fMin <= fOff && fOff <= fMax;
3752 bool SvNumberformat::ImpGetDateOutput(double fNumber,
3753 sal_uInt16 nIx,
3754 bool bStarFlag,
3755 const NativeNumberWrapper& rNatNum,
3756 const SvNFLanguageData& rCurrentLang,
3757 OUStringBuffer& sBuff) const
3759 using namespace ::com::sun::star::i18n;
3760 bool bRes = false;
3762 CalendarWrapper& rCal = *rCurrentLang.GetCalendar();
3763 if (!lcl_getValidDate( DateTime( rScan.GetNullDate() ), rCal.getEpochStart(), fNumber))
3765 sBuff = ImpSvNumberformatScan::sErrStr;
3766 return false;
3768 rCal.setLocalDateTime( fNumber );
3769 int nUseMonthCase = 0; // Not decided yet
3770 OUString aOrgCalendar; // empty => not changed yet
3772 double fOrgDateTime(0.0);
3773 bool bOtherCalendar = ImpIsOtherCalendar( NumFor[nIx], *rCurrentLang.GetCalendar() );
3774 if ( bOtherCalendar )
3776 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
3778 if ( ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() ) )
3780 bOtherCalendar = false;
3782 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
3783 const sal_uInt16 nCnt = NumFor[nIx].GetCount();
3784 sal_Int16 nNatNum = NumFor[nIx].GetNatNum().GetNatNum();
3785 OUString aStr;
3787 // NatNum12: if the date format contains more than a date
3788 // field, it needs to specify in NatNum12 argument
3789 // which date element needs special formatting:
3791 // '[NatNum12 ordinal-number]D' -> "1st"
3792 // '[NatNum12 D=ordinal-number]D" of "MMMM' -> "1st of April"
3793 // '[NatNum12 D=ordinal]D" of "MMMM' -> "first of April"
3794 // '[NatNum12 YYYY=year,D=ordinal]D" of "MMMM", "YYYY' -> "first of April, nineteen ninety"
3796 // Note: set only for YYYY, MMMM, M, DDDD, D and NNN/AAAA in date formats.
3797 // Additionally for MMMMM, MMM, DDD and NN/AA to support at least
3798 // capitalize, upper, lower, title.
3799 // XXX It's possible to extend this for other keywords and date + time
3800 // combinations, as required.
3802 bool bUseSpellout = NatNumTakesParameters(nNatNum) &&
3803 (nCnt == 1 || NumFor[nIx].GetNatNum().GetParams().indexOf('=') > -1);
3805 for (sal_uInt16 i = 0; i < nCnt; i++)
3807 switch (rInfo.nTypeArray[i])
3809 case NF_SYMBOLTYPE_CALENDAR :
3810 if ( !aOrgCalendar.getLength() )
3812 aOrgCalendar = rCal.getUniqueID();
3813 fOrgDateTime = rCal.getDateTime();
3815 rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLanguageTag().getLocale() );
3816 rCal.setDateTime( fOrgDateTime );
3817 ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
3818 break;
3819 case NF_SYMBOLTYPE_STAR:
3820 if( bStarFlag )
3822 bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]);
3824 break;
3825 case NF_SYMBOLTYPE_BLANK:
3826 if (rInfo.sStrArray[i].getLength() >= 2)
3827 InsertBlanks( sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] );
3828 break;
3829 case NF_SYMBOLTYPE_STRING:
3830 case NF_SYMBOLTYPE_CURRENCY:
3831 case NF_SYMBOLTYPE_DATESEP:
3832 case NF_SYMBOLTYPE_TIMESEP:
3833 case NF_SYMBOLTYPE_TIME100SECSEP:
3834 sBuff.append(rInfo.sStrArray[i]);
3835 break;
3836 case NF_KEY_M: // M
3837 aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_MONTH, nNatNum );
3838 // NatNum12: support variants of preposition, suffixation or article
3839 // for example, Catalan "de març", but "d'abril" etc.
3840 if ( bUseSpellout )
3842 aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i], rNatNum);
3844 sBuff.append(aStr);
3845 break;
3846 case NF_KEY_MM: // MM
3847 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_MONTH, nNatNum ));
3848 break;
3849 case NF_KEY_MMM: // MMM
3850 case NF_KEY_MMMM: // MMMM
3851 case NF_KEY_MMMMM: // MMMMM
3852 // NatNum12: support variants of preposition, suffixation or
3853 // article, or capitalize, upper, lower, title.
3854 // Note: result of the "spell out" conversion can depend from the optional
3855 // PartitiveMonths or GenitiveMonths defined in the locale data,
3856 // see description of ImpUseMonthCase(), and locale data in
3857 // i18npool/source/localedata/data/ and libnumbertext
3858 aStr = rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx],
3859 static_cast<NfKeywordIndex>(rInfo.nTypeArray[i])),
3860 nNatNum);
3861 if ( bUseSpellout )
3863 aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i], rNatNum);
3865 sBuff.append(aStr);
3866 break;
3867 case NF_KEY_Q: // Q
3868 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_QUARTER, nNatNum ));
3869 break;
3870 case NF_KEY_QQ: // QQ
3871 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_QUARTER, nNatNum ));
3872 break;
3873 case NF_KEY_D: // D
3874 aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY, nNatNum );
3875 // NatNum12: support variants of preposition, suffixation or article
3876 if ( bUseSpellout )
3878 aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i], rNatNum);
3880 sBuff.append(aStr);
3881 break;
3882 case NF_KEY_DD: // DD
3883 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY, nNatNum ));
3884 break;
3885 case NF_KEY_DDD: // DDD
3886 if ( bOtherCalendar )
3888 SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
3890 aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum );
3891 // NatNum12: support at least capitalize, upper, lower, title
3892 if ( bUseSpellout )
3894 aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i], rNatNum);
3896 sBuff.append(aStr);
3897 if ( bOtherCalendar )
3899 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
3901 break;
3902 case NF_KEY_DDDD: // DDDD
3903 if ( bOtherCalendar )
3905 SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
3907 aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum );
3908 // NatNum12: support variants of preposition, suffixation or article
3909 if ( bUseSpellout )
3911 aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i], rNatNum);
3913 sBuff.append(aStr);
3914 if ( bOtherCalendar )
3916 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
3918 break;
3919 case NF_KEY_YY: // YY
3920 if ( bOtherCalendar )
3922 SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
3924 // Prepend a minus sign if Gregorian BCE and era is not displayed.
3925 if (lcl_isSignedYear( rCal, NumFor[nIx] ))
3927 sBuff.append('-');
3929 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum ));
3930 if ( bOtherCalendar )
3932 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
3934 break;
3935 case NF_KEY_YYYY: // YYYY
3936 if ( bOtherCalendar )
3938 SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
3940 // Prepend a minus sign if Gregorian BCE and era is not displayed.
3941 if (lcl_isSignedYear( rCal, NumFor[nIx] ))
3943 sBuff.append('-');
3945 aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum );
3946 if (aStr.getLength() < 4 && !lcl_hasEra(NumFor[nIx]))
3948 using namespace comphelper::string;
3949 // Ensure that year consists of at least 4 digits, so it
3950 // can be distinguished from 2 digits display and edited
3951 // without suddenly being hit by the 2-digit year magic.
3952 OUStringBuffer aBuf;
3953 padToLength(aBuf, 4 - aStr.getLength(), '0');
3954 ::impTransliterate(aBuf, NumFor[nIx].GetNatNum(), rNatNum);
3955 aBuf.append(aStr);
3956 aStr = aBuf.makeStringAndClear();
3958 // NatNum12: support variants of preposition, suffixation or article
3959 if ( bUseSpellout )
3961 aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i], rNatNum);
3963 sBuff.append(aStr);
3964 if ( bOtherCalendar )
3966 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
3968 break;
3969 case NF_KEY_EC: // E
3970 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum ));
3971 break;
3972 case NF_KEY_EEC: // EE
3973 case NF_KEY_R: // R
3974 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum ));
3975 break;
3976 case NF_KEY_NN: // NN
3977 case NF_KEY_AAA: // AAA
3978 aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum );
3979 // NatNum12: support at least capitalize, upper, lower, title
3980 if ( bUseSpellout )
3982 aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i], rNatNum);
3984 sBuff.append(aStr);
3985 break;
3986 case NF_KEY_NNN: // NNN
3987 case NF_KEY_AAAA: // AAAA
3988 aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum );
3989 // NatNum12: support variants of preposition, suffixation or article
3990 if ( bUseSpellout )
3992 aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i], rNatNum);
3994 sBuff.append(aStr);
3995 break;
3996 case NF_KEY_NNNN: // NNNN
3997 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ));
3998 sBuff.append(rLoc().getLongDateDayOfWeekSep());
3999 break;
4000 case NF_KEY_WW : // WW
4001 sBuff.append(ImpIntToString(rNatNum, nIx,
4002 rCal.getValue( CalendarFieldIndex::WEEK_OF_YEAR )));
4003 break;
4004 case NF_KEY_G: // G
4005 ImpAppendEraG(sBuff, rCal, nNatNum );
4006 break;
4007 case NF_KEY_GG: // GG
4008 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum ));
4009 break;
4010 case NF_KEY_GGG: // GGG
4011 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_ERA, nNatNum ));
4012 break;
4013 case NF_KEY_RR: // RR => GGGEE
4014 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR_AND_ERA, nNatNum ));
4015 break;
4018 if ( aOrgCalendar.getLength() )
4020 rCal.loadCalendar( aOrgCalendar, rLoc().getLanguageTag().getLocale() ); // restore calendar
4022 return bRes;
4025 bool SvNumberformat::ImpGetDateTimeOutput(double fNumber,
4026 sal_uInt16 nIx,
4027 bool bStarFlag,
4028 const NativeNumberWrapper& rNatNum,
4029 const SvNFLanguageData& rCurrentLang,
4030 OUStringBuffer& sBuff) const
4032 using namespace ::com::sun::star::i18n;
4033 bool bRes = false;
4035 CalendarWrapper& rCal = *rCurrentLang.GetCalendar();
4036 if (!lcl_getValidDate( DateTime( rScan.GetNullDate() ), rCal.getEpochStart(), fNumber))
4038 sBuff = ImpSvNumberformatScan::sErrStr;
4039 return false;
4042 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
4043 bool bInputLine;
4044 sal_Int32 nCntPost, nFirstRounding;
4045 if ( rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION &&
4046 0 < rInfo.nCntPost && rInfo.nCntPost < kTimeSignificantRound )
4048 bInputLine = true;
4049 nCntPost = nFirstRounding = kTimeSignificantRound;
4051 else
4053 bInputLine = false;
4054 nCntPost = rInfo.nCntPost;
4055 // For clock format (not []) do not round up to seconds and thus days.
4056 nFirstRounding = (rInfo.bThousand ? nCntPost : kTimeSignificantRound);
4058 double fTime = (fNumber - floor( fNumber )) * 86400.0;
4059 fTime = ::rtl::math::round( fTime, int(nFirstRounding) );
4060 if (fTime >= 86400.0)
4062 // result of fNumber==x.999999999... rounded up, use correct date/time
4063 fTime -= 86400.0;
4064 fNumber = floor( fNumber + 0.5) + fTime;
4066 rCal.setLocalDateTime( fNumber );
4068 int nUseMonthCase = 0; // Not decided yet
4069 OUString aOrgCalendar; // empty => not changed yet
4070 double fOrgDateTime(0.0);
4071 bool bOtherCalendar = ImpIsOtherCalendar( NumFor[nIx], *rCurrentLang.GetCalendar() );
4072 if ( bOtherCalendar )
4074 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
4076 if ( ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() ) )
4078 bOtherCalendar = false;
4080 sal_Int16 nNatNum = NumFor[nIx].GetNatNum().GetNatNum();
4082 OUStringBuffer sSecStr;
4083 sal_Int32 nSecPos = 0; // For figure by figure processing
4084 sal_uInt32 nHour, nMin, nSec;
4085 if (!rInfo.bThousand) // No [] format
4087 sal_uInt16 nCHour, nCMinute, nCSecond;
4088 double fFractionOfSecond;
4089 tools::Time::GetClock( fNumber, nCHour, nCMinute, nCSecond, fFractionOfSecond, nCntPost);
4090 nHour = nCHour;
4091 nMin = nCMinute;
4092 nSec = nCSecond;
4093 nCntPost = ImpGetFractionOfSecondString( sSecStr, fFractionOfSecond, nCntPost, true, nIx,
4094 (bInputLine ? rInfo.nCntPost : 0), rNatNum );
4096 else
4098 sal_uInt32 nSeconds = static_cast<sal_uInt32>(floor( fTime ));
4100 nCntPost = ImpGetFractionOfSecondString( sSecStr, fTime - nSeconds, nCntPost, false, nIx,
4101 (bInputLine ? rInfo.nCntPost : 0), rNatNum );
4103 if (rInfo.nThousand == 3) // [ss]
4105 nHour = 0;
4106 nMin = 0;
4107 nSec = nSeconds;
4109 else if (rInfo.nThousand == 2) // [mm]:ss
4111 nHour = 0;
4112 nMin = nSeconds / 60;
4113 nSec = nSeconds % 60;
4115 else if (rInfo.nThousand == 1) // [hh]:mm:ss
4117 nHour = nSeconds / 3600;
4118 nMin = (nSeconds%3600) / 60;
4119 nSec = nSeconds%60;
4121 else
4123 nHour = 0; // TODO What should these values be?
4124 nMin = 0;
4125 nSec = 0;
4128 sal_Unicode cAmPm = ' '; // a or p
4129 if (rInfo.nCntExp) // AM/PM
4131 if (nHour == 0)
4133 nHour = 12;
4134 cAmPm = 'a';
4136 else if (nHour < 12)
4138 cAmPm = 'a';
4140 else
4142 cAmPm = 'p';
4143 if (nHour > 12)
4145 nHour -= 12;
4149 const sal_uInt16 nCnt = NumFor[nIx].GetCount();
4150 sal_Int32 nLen;
4151 OUString aYear;
4152 for (sal_uInt16 i = 0; i < nCnt; i++)
4154 switch (rInfo.nTypeArray[i])
4156 case NF_SYMBOLTYPE_CALENDAR :
4157 if ( !aOrgCalendar.getLength() )
4159 aOrgCalendar = rCal.getUniqueID();
4160 fOrgDateTime = rCal.getDateTime();
4162 rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLanguageTag().getLocale() );
4163 rCal.setDateTime( fOrgDateTime );
4164 ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
4165 break;
4166 case NF_SYMBOLTYPE_STAR:
4167 if( bStarFlag )
4169 bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]);
4171 break;
4172 case NF_SYMBOLTYPE_BLANK:
4173 if (rInfo.sStrArray[i].getLength() >= 2)
4174 InsertBlanks( sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] );
4175 break;
4176 case NF_SYMBOLTYPE_STRING:
4177 case NF_SYMBOLTYPE_CURRENCY:
4178 case NF_SYMBOLTYPE_DATESEP:
4179 case NF_SYMBOLTYPE_TIMESEP:
4180 case NF_SYMBOLTYPE_TIME100SECSEP:
4181 sBuff.append(rInfo.sStrArray[i]);
4182 break;
4183 case NF_SYMBOLTYPE_DIGIT:
4184 nLen = ( bInputLine && i > 0 &&
4185 (rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING ||
4186 rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_TIME100SECSEP) ?
4187 nCntPost : rInfo.sStrArray[i].getLength() );
4188 for (sal_Int32 j = 0; j < nLen && nSecPos < nCntPost && nSecPos < sSecStr.getLength(); ++j)
4190 sBuff.append(sSecStr[ nSecPos ]);
4191 nSecPos++;
4193 break;
4194 case NF_KEY_AMPM: // AM/PM
4195 if (cAmPm == 'a')
4197 sBuff.append(rCal.getDisplayName( CalendarDisplayIndex::AM_PM,
4198 AmPmValue::AM, 0 ));
4200 else
4202 sBuff.append(rCal.getDisplayName( CalendarDisplayIndex::AM_PM,
4203 AmPmValue::PM, 0 ));
4205 break;
4206 case NF_KEY_AP: // A/P
4207 if (cAmPm == 'a')
4209 sBuff.append('a');
4211 else
4213 sBuff.append('p');
4215 break;
4216 case NF_KEY_MI: // M
4217 sBuff.append(ImpIntToString(rNatNum, nIx, nMin ));
4218 break;
4219 case NF_KEY_MMI: // MM
4220 sBuff.append(ImpIntToString(rNatNum, nIx, nMin, 2 ));
4221 break;
4222 case NF_KEY_H: // H
4223 sBuff.append(ImpIntToString(rNatNum, nIx, nHour ));
4224 break;
4225 case NF_KEY_HH: // HH
4226 sBuff.append(ImpIntToString(rNatNum, nIx, nHour, 2 ));
4227 break;
4228 case NF_KEY_S: // S
4229 sBuff.append(ImpIntToString(rNatNum, nIx, nSec ));
4230 break;
4231 case NF_KEY_SS: // SS
4232 sBuff.append(ImpIntToString(rNatNum, nIx, nSec, 2 ));
4233 break;
4234 case NF_KEY_M: // M
4235 sBuff.append(rCal.getDisplayString(
4236 CalendarDisplayCode::SHORT_MONTH, nNatNum ));
4237 break;
4238 case NF_KEY_MM: // MM
4239 sBuff.append(rCal.getDisplayString(
4240 CalendarDisplayCode::LONG_MONTH, nNatNum ));
4241 break;
4242 case NF_KEY_MMM: // MMM
4243 sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx],
4244 static_cast<NfKeywordIndex>(rInfo.nTypeArray[i])),
4245 nNatNum));
4246 break;
4247 case NF_KEY_MMMM: // MMMM
4248 sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx],
4249 static_cast<NfKeywordIndex>(rInfo.nTypeArray[i])),
4250 nNatNum));
4251 break;
4252 case NF_KEY_MMMMM: // MMMMM
4253 sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx],
4254 static_cast<NfKeywordIndex>(rInfo.nTypeArray[i])),
4255 nNatNum));
4256 break;
4257 case NF_KEY_Q: // Q
4258 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_QUARTER, nNatNum ));
4259 break;
4260 case NF_KEY_QQ: // QQ
4261 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_QUARTER, nNatNum ));
4262 break;
4263 case NF_KEY_D: // D
4264 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY, nNatNum ));
4265 break;
4266 case NF_KEY_DD: // DD
4267 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY, nNatNum ));
4268 break;
4269 case NF_KEY_DDD: // DDD
4270 if ( bOtherCalendar )
4272 SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
4274 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum ));
4275 if ( bOtherCalendar )
4277 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
4279 break;
4280 case NF_KEY_DDDD: // DDDD
4281 if ( bOtherCalendar )
4283 SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
4285 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ));
4286 if ( bOtherCalendar )
4288 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
4290 break;
4291 case NF_KEY_YY: // YY
4292 if ( bOtherCalendar )
4294 SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
4296 // Prepend a minus sign if Gregorian BCE and era is not displayed.
4297 if (lcl_isSignedYear( rCal, NumFor[nIx] ))
4299 sBuff.append('-');
4301 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum ));
4302 if ( bOtherCalendar )
4304 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
4306 break;
4307 case NF_KEY_YYYY: // YYYY
4308 if ( bOtherCalendar )
4310 SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
4312 // Prepend a minus sign if Gregorian BCE and era is not displayed.
4313 if (lcl_isSignedYear( rCal, NumFor[nIx] ))
4315 sBuff.append('-');
4317 aYear = rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum );
4318 if (aYear.getLength() < 4 && !lcl_hasEra(NumFor[nIx]))
4320 using namespace comphelper::string;
4321 // Ensure that year consists of at least 4 digits, so it
4322 // can be distinguished from 2 digits display and edited
4323 // without suddenly being hit by the 2-digit year magic.
4324 OUStringBuffer aBuf;
4325 padToLength(aBuf, 4 - aYear.getLength(), '0');
4326 ::impTransliterate(aBuf, NumFor[nIx].GetNatNum(), rNatNum);
4327 aBuf.append(aYear);
4328 sBuff.append(aBuf);
4330 else
4332 sBuff.append(aYear);
4334 if ( bOtherCalendar )
4336 SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime, *rCurrentLang.GetCalendar() );
4338 break;
4339 case NF_KEY_EC: // E
4340 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum ));
4341 break;
4342 case NF_KEY_EEC: // EE
4343 case NF_KEY_R: // R
4344 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum ));
4345 break;
4346 case NF_KEY_NN: // NN
4347 case NF_KEY_AAA: // AAA
4348 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum ));
4349 break;
4350 case NF_KEY_NNN: // NNN
4351 case NF_KEY_AAAA: // AAAA
4352 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ));
4353 break;
4354 case NF_KEY_NNNN: // NNNN
4355 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ));
4356 sBuff.append(rLoc().getLongDateDayOfWeekSep());
4357 break;
4358 case NF_KEY_WW : // WW
4359 sBuff.append(ImpIntToString(rNatNum, nIx, rCal.getValue( CalendarFieldIndex::WEEK_OF_YEAR )));
4360 break;
4361 case NF_KEY_G: // G
4362 ImpAppendEraG( sBuff, rCal, nNatNum );
4363 break;
4364 case NF_KEY_GG: // GG
4365 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum ));
4366 break;
4367 case NF_KEY_GGG: // GGG
4368 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_ERA, nNatNum ));
4369 break;
4370 case NF_KEY_RR: // RR => GGGEE
4371 sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR_AND_ERA, nNatNum ));
4372 break;
4375 if ( aOrgCalendar.getLength() )
4377 rCal.loadCalendar( aOrgCalendar, rLoc().getLanguageTag().getLocale() ); // restore calendar
4379 return bRes;
4382 bool SvNumberformat::ImpGetLogicalOutput(double fNumber,
4383 sal_uInt16 nIx,
4384 const NativeNumberWrapper& rNatNum,
4385 OUStringBuffer& sStr) const
4387 bool bRes = false;
4388 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
4389 const sal_uInt16 nCnt = NumFor[nIx].GetCount();
4390 for (sal_uInt16 j = 0; j < nCnt; ++j)
4392 switch (rInfo.nTypeArray[j])
4394 case NF_KEY_BOOLEAN:
4395 sStr.append( fNumber ? rScan.GetTrueString() : rScan.GetFalseString());
4396 break;
4397 case NF_SYMBOLTYPE_STRING:
4398 sStr.append( rInfo.sStrArray[j]);
4399 break;
4402 ::impTransliterate(sStr, NumFor[nIx].GetNatNum(), rNatNum);
4403 return bRes;
4406 bool SvNumberformat::ImpGetNumberOutput(double fNumber,
4407 sal_uInt16 nIx,
4408 bool bStarFlag,
4409 const NativeNumberWrapper& rNatNum,
4410 OUStringBuffer& sStr) const
4412 bool bRes = false;
4413 bool bSign;
4414 if (fNumber < 0.0)
4416 bSign = (nIx == 0); // Not in the ones at the back;
4417 fNumber = -fNumber;
4419 else
4421 bSign = false;
4422 if ( std::signbit( fNumber ) )
4424 fNumber = -fNumber; // yes, -0.0 is possible, eliminate '-'
4427 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
4428 if (rInfo.eScannedType == SvNumFormatType::PERCENT)
4430 if (fNumber < D_MAX_D_BY_100)
4432 fNumber *= 100.0;
4434 else
4436 sStr = ImpSvNumberformatScan::sErrStr;
4437 return false;
4440 sal_uInt16 i, j;
4441 sal_Int32 nDecPos = -1;
4442 bool bInteger = false;
4443 if ( rInfo.nThousand != FLAG_STANDARD_IN_FORMAT )
4445 // Special formatting only if no GENERAL keyword in format code
4446 const sal_uInt16 nThousand = rInfo.nThousand;
4447 tools::Long nPrecExp;
4448 for (i = 0; i < nThousand; i++)
4450 if (fNumber > D_MIN_M_BY_1000)
4452 fNumber /= 1000.0;
4454 else
4456 fNumber = 0.0;
4459 if (fNumber > 0.0)
4461 nPrecExp = GetPrecExp( fNumber );
4463 else
4465 nPrecExp = 0;
4467 // Make sure that Calc's ROUND and formatted output agree
4468 fNumber = rtl_math_round(fNumber, rInfo.nCntPost, rtl_math_RoundingMode_Corrected);
4469 if (rInfo.nCntPost) // Decimal places
4471 if ((rInfo.nCntPost + nPrecExp) > 15 && nPrecExp < 15)
4473 sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, 15-nPrecExp, '.');
4474 for (tools::Long l = 15-nPrecExp; l < static_cast<tools::Long>(rInfo.nCntPost); l++)
4476 sStr.append('0');
4479 else
4481 sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, rInfo.nCntPost, '.' );
4483 sStr.stripStart('0'); // Strip leading zeros
4485 else if (fNumber == 0.0) // Null
4487 // Nothing to be done here, keep empty string sStr,
4488 // ImpNumberFillWithThousands does the rest
4490 else // Integer
4492 sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, 0, '.');
4493 sStr.stripStart('0'); // Strip leading zeros
4495 nDecPos = sStr.indexOf('.' );
4496 if ( nDecPos >= 0)
4498 const sal_Unicode* p = sStr.getStr() + nDecPos;
4499 while ( *++p == '0' )
4501 if ( !*p )
4503 bInteger = true;
4505 sStr.remove( nDecPos, 1 ); // Remove .
4507 if (bSign && (sStr.isEmpty() || checkForAll0s(sStr))) // Only 00000
4509 bSign = false; // Not -0.00
4511 } // End of != FLAG_STANDARD_IN_FORMAT
4513 // Edit backwards:
4514 j = NumFor[nIx].GetCount()-1; // Last symbol
4515 // Decimal places:
4516 bRes |= ImpDecimalFill(rNatNum, sStr, fNumber, nDecPos, j, nIx, bInteger, bStarFlag);
4517 if (bSign)
4519 sStr.insert(0, '-');
4521 ::impTransliterate(sStr, NumFor[nIx].GetNatNum(), rNatNum);
4522 return bRes;
4525 bool SvNumberformat::ImpDecimalFill(const NativeNumberWrapper& rNatNum,
4526 OUStringBuffer& sStr, // number string
4527 double& rNumber, // number
4528 sal_Int32 nDecPos, // decimals start
4529 sal_uInt16 j, // symbol index within format code
4530 sal_uInt16 nIx, // subformat index
4531 bool bInteger, // is integer
4532 bool bStarFlag) const
4534 bool bRes = false;
4535 bool bFilled = false; // Was filled?
4536 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
4537 sal_Int32 k = sStr.getLength(); // After last figure
4538 // Decimal places:
4539 if (rInfo.nCntPost > 0)
4541 bool bTrailing = true; // Trailing zeros?
4542 short nType;
4543 while (j > 0 && // Backwards
4544 (nType = rInfo.nTypeArray[j]) != NF_SYMBOLTYPE_DECSEP)
4546 switch ( nType )
4548 case NF_SYMBOLTYPE_STAR:
4549 if( bStarFlag )
4551 bRes = lcl_insertStarFillChar( sStr, k, rInfo.sStrArray[j]);
4553 break;
4554 case NF_SYMBOLTYPE_BLANK:
4555 if (rInfo.sStrArray[j].getLength() >= 2)
4556 /*k = */ InsertBlanks(sStr, k, rInfo.sStrArray[j][1] );
4557 break;
4558 case NF_SYMBOLTYPE_STRING:
4559 case NF_SYMBOLTYPE_CURRENCY:
4560 case NF_SYMBOLTYPE_PERCENT:
4561 sStr.insert(k, rInfo.sStrArray[j]);
4562 break;
4563 case NF_SYMBOLTYPE_THSEP:
4564 if (rInfo.nThousand == 0)
4566 sStr.insert(k, rInfo.sStrArray[j]);
4568 break;
4569 case NF_SYMBOLTYPE_DIGIT:
4571 const OUString& rStr = rInfo.sStrArray[j];
4572 const sal_Unicode* p1 = rStr.getStr();
4573 const sal_Unicode* p = p1 + rStr.getLength();
4574 // In case the number of decimals passed are less than the
4575 // "digits" given, append trailing '0' characters, which here
4576 // means insert them because literal strings may have been
4577 // appended already. If they weren't to be '0' characters
4578 // they'll be changed below, as if decimals with trailing zeros
4579 // were passed.
4580 if (nDecPos >= 0 && nDecPos <= k)
4582 sal_Int32 nAppend = rStr.getLength() - (k - nDecPos);
4583 while (nAppend-- > 0)
4585 sStr.insert( k++, '0');
4588 while (k && p1 < p--)
4590 const sal_Unicode c = *p;
4591 k--;
4592 if ( sStr[k] != '0' )
4594 bTrailing = false;
4595 bFilled = true;
4597 if (bTrailing)
4599 if ( c == '0' )
4601 bFilled = true;
4603 else if ( c == '-' )
4605 if ( bInteger )
4607 sStr[ k ] = '-';
4609 bFilled = true;
4611 else if ( c == '?' )
4613 sStr[ k ] = cBlankDigit;
4614 bFilled = true;
4616 else if ( !bFilled ) // #
4618 sStr.remove(k,1);
4621 } // of for
4622 break;
4623 } // of case digi
4624 case NF_KEY_CCC: // CCC currency
4625 sStr.insert(k, rScan.GetCurAbbrev());
4626 break;
4627 case NF_KEY_GENERAL: // Standard in the String
4629 OUStringBuffer sNum;
4630 ImpGetOutputStandard(rNumber, sNum, rNatNum);
4631 sNum.stripStart('-');
4632 sStr.insert(k, sNum);
4633 break;
4635 default:
4636 break;
4637 } // of switch
4638 j--;
4639 } // of while
4640 } // of decimal places
4642 bRes |= ImpNumberFillWithThousands(rNatNum, sStr, rNumber, k, j, nIx, // Fill with . if needed
4643 rInfo.nCntPre, bStarFlag, bFilled );
4645 return bRes;
4648 bool SvNumberformat::ImpNumberFillWithThousands( const NativeNumberWrapper& rNatNum,
4649 OUStringBuffer& sBuff, // number string
4650 double& rNumber, // number
4651 sal_Int32 k, // position within string
4652 sal_uInt16 j, // symbol index within format code
4653 sal_uInt16 nIx, // subformat index
4654 sal_Int32 nDigCnt, // count of integer digits in format
4655 bool bStarFlag,
4656 bool bAddDecSep) const // add decimal separator if necessary
4658 bool bRes = false;
4659 sal_Int32 nLeadingStringChars = 0; // inserted StringChars before number
4660 sal_Int32 nDigitCount = 0; // count of integer digits from the right
4661 bool bStop = false;
4662 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
4663 // no normal thousands separators if number divided by thousands
4664 bool bDoThousands = (rInfo.nThousand == 0);
4665 utl::DigitGroupingIterator aGrouping( GetCurrentLanguageData().GetLocaleData()->getDigitGrouping());
4667 while (!bStop) // backwards
4669 if (j == 0)
4671 bStop = true;
4673 switch (rInfo.nTypeArray[j])
4675 case NF_SYMBOLTYPE_DECSEP:
4676 aGrouping.reset();
4677 [[fallthrough]];
4678 case NF_SYMBOLTYPE_STRING:
4679 if ( rInfo.nTypeArray[j] == NF_SYMBOLTYPE_STRING && nDigCnt == 0 )
4681 // tdf#159930 no integer in format ".###"
4682 k = 0; // insert string at the beginning
4684 [[fallthrough]];
4685 case NF_SYMBOLTYPE_CURRENCY:
4686 case NF_SYMBOLTYPE_PERCENT:
4687 if ( rInfo.nTypeArray[j] != NF_SYMBOLTYPE_DECSEP || bAddDecSep )
4688 sBuff.insert(k, rInfo.sStrArray[j]);
4689 if ( k == 0 )
4691 nLeadingStringChars = nLeadingStringChars + rInfo.sStrArray[j].getLength();
4693 break;
4694 case NF_SYMBOLTYPE_STAR:
4695 if( bStarFlag )
4697 bRes = lcl_insertStarFillChar( sBuff, k, rInfo.sStrArray[j]);
4699 break;
4700 case NF_SYMBOLTYPE_BLANK:
4701 if (rInfo.sStrArray[j].getLength() >= 2)
4702 /*k = */ InsertBlanks(sBuff, k, rInfo.sStrArray[j][1] );
4703 break;
4704 case NF_SYMBOLTYPE_THSEP:
4705 // #i7284# #102685# Insert separator also if number is divided
4706 // by thousands and the separator is specified somewhere in
4707 // between and not only at the end.
4708 // #i12596# But do not insert if it's a parenthesized negative
4709 // format like (#,)
4710 // In fact, do not insert if divided and regex [0#,],[^0#] and
4711 // no other digit symbol follows (which was already detected
4712 // during scan of format code, otherwise there would be no
4713 // division), else do insert. Same in ImpNumberFill() below.
4714 if ( !bDoThousands && j < NumFor[nIx].GetCount()-1 )
4716 bDoThousands = ((j == 0) ||
4717 (rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_DIGIT &&
4718 rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_THSEP) ||
4719 (rInfo.nTypeArray[j+1] == NF_SYMBOLTYPE_DIGIT));
4721 if ( bDoThousands )
4723 if (k > 0)
4725 sBuff.insert(k, rInfo.sStrArray[j]);
4727 else if (nDigitCount < nDigCnt)
4729 // Leading '#' displays nothing (e.g. no leading
4730 // separator for numbers <1000 with #,##0 format).
4731 // Leading '?' displays blank.
4732 // Everything else, including nothing, displays the
4733 // separator.
4734 sal_Unicode cLeader = 0;
4735 if (j > 0 && rInfo.nTypeArray[j-1] == NF_SYMBOLTYPE_DIGIT)
4737 const OUString& rStr = rInfo.sStrArray[j-1];
4738 sal_Int32 nLen = rStr.getLength();
4739 if (nLen)
4741 cLeader = rStr[ nLen - 1 ];
4744 switch (cLeader)
4746 case '#':
4747 ; // nothing
4748 break;
4749 case '?':
4750 // replace thousand separator with blank
4751 sBuff.insert(k, ' ');
4752 break;
4753 default:
4754 sBuff.insert(k, rInfo.sStrArray[j]);
4757 aGrouping.advance();
4759 break;
4760 case NF_SYMBOLTYPE_DIGIT:
4762 const OUString& rStr = rInfo.sStrArray[j];
4763 const sal_Unicode* p1 = rStr.getStr();
4764 const sal_Unicode* p = p1 + rStr.getLength();
4765 while ( p1 < p-- )
4767 nDigitCount++;
4768 if (k > 0)
4770 k--;
4772 else
4774 switch (*p)
4776 case '0':
4777 sBuff.insert(0, '0');
4778 break;
4779 case '?':
4780 sBuff.insert(0, cBlankDigit);
4781 break;
4784 if (nDigitCount == nDigCnt && k > 0)
4786 // more digits than specified
4787 ImpDigitFill(sBuff, 0, k, nIx, nDigitCount, aGrouping);
4790 break;
4792 case NF_KEY_CCC: // CCC currency
4793 sBuff.insert(k, rScan.GetCurAbbrev());
4794 break;
4795 case NF_KEY_GENERAL: // "General" in string
4797 OUStringBuffer sNum;
4798 ImpGetOutputStandard(rNumber, sNum, rNatNum);
4799 sNum.stripStart('-');
4800 sBuff.insert(k, sNum);
4801 break;
4803 default:
4804 break;
4805 } // switch
4806 j--; // next format code string
4807 } // while
4809 k = k + nLeadingStringChars; // MSC converts += to int and then warns, so ...
4810 if (k > nLeadingStringChars)
4812 ImpDigitFill(sBuff, nLeadingStringChars, k, nIx, nDigitCount, aGrouping);
4814 return bRes;
4817 void SvNumberformat::ImpDigitFill(OUStringBuffer& sStr, // number string
4818 sal_Int32 nStart, // start of digits
4819 sal_Int32 & k, // position within string
4820 sal_uInt16 nIx, // subformat index
4821 sal_Int32 & nDigitCount, // count of integer digits from the right so far
4822 utl::DigitGroupingIterator & rGrouping ) const // current grouping
4824 if (NumFor[nIx].Info().bThousand) // Only if grouping fill in separators
4826 const OUString& rThousandSep = GetCurrentLanguageData().GetNumThousandSep();
4827 while (k > nStart)
4829 if (nDigitCount == rGrouping.getPos())
4831 sStr.insert( k, rThousandSep );
4832 rGrouping.advance();
4834 nDigitCount++;
4835 k--;
4838 else // simply skip
4840 k = nStart;
4844 bool SvNumberformat::ImpNumberFill( const NativeNumberWrapper& rNatNum,
4845 OUStringBuffer& sBuff, // number string
4846 double& rNumber, // number for "General" format
4847 sal_Int32& k, // position within string
4848 sal_uInt16& j, // symbol index within format code
4849 sal_uInt16 nIx, // subformat index
4850 short eSymbolType, // type of stop condition
4851 bool bStarFlag,
4852 bool bInsertRightBlank) const // insert blank on right for denominator (default = false)
4854 bool bRes = false;
4855 bool bStop = false;
4856 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
4857 // no normal thousands separators if number divided by thousands
4858 bool bDoThousands = (rInfo.nThousand == 0);
4859 bool bFoundNumber = false;
4860 short nType;
4862 k = sBuff.getLength(); // behind last digit
4864 while (!bStop && (nType = rInfo.nTypeArray[j]) != eSymbolType ) // Backwards
4866 switch ( nType )
4868 case NF_SYMBOLTYPE_STAR:
4869 if( bStarFlag )
4871 if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP )
4872 k = 0; // tdf#100842 jump to beginning of number before inserting something else
4873 bRes = lcl_insertStarFillChar( sBuff, k, rInfo.sStrArray[j]);
4875 break;
4876 case NF_SYMBOLTYPE_BLANK:
4877 if (rInfo.sStrArray[j].getLength() >= 2)
4879 if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP )
4880 k = 0; // tdf#100842 jump to beginning of number before inserting something else
4881 k = InsertBlanks(sBuff, k, rInfo.sStrArray[j][1] );
4883 break;
4884 case NF_SYMBOLTYPE_THSEP:
4885 // Same as in ImpNumberFillWithThousands() above, do not insert
4886 // if divided and regex [0#,],[^0#] and no other digit symbol
4887 // follows (which was already detected during scan of format
4888 // code, otherwise there would be no division), else do insert.
4889 if ( !bDoThousands && j < NumFor[nIx].GetCount()-1 )
4891 bDoThousands = ((j == 0) ||
4892 (rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_DIGIT &&
4893 rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_THSEP) ||
4894 (rInfo.nTypeArray[j+1] == NF_SYMBOLTYPE_DIGIT));
4896 if ( bDoThousands && k > 0 )
4898 sBuff.insert(k, rInfo.sStrArray[j]);
4900 break;
4901 case NF_SYMBOLTYPE_DIGIT:
4903 bFoundNumber = true;
4904 sal_uInt16 nPosInsertBlank = bInsertRightBlank ? k : 0; // left alignment of denominator
4905 const OUString& rStr = rInfo.sStrArray[j];
4906 const sal_Unicode* p1 = rStr.getStr();
4907 const sal_Unicode* p = p1 + rStr.getLength();
4908 while ( p1 < p-- )
4910 if (k > 0)
4912 k--;
4914 else
4916 switch (*p)
4918 case '0':
4919 sBuff.insert(0, '0');
4920 break;
4921 case '?':
4922 sBuff.insert(nPosInsertBlank, cBlankDigit);
4923 break;
4928 break;
4929 case NF_KEY_CCC: // CCC currency
4930 sBuff.insert(k, rScan.GetCurAbbrev());
4931 break;
4932 case NF_KEY_GENERAL: // Standard in the String
4934 OUStringBuffer sNum;
4935 bFoundNumber = true;
4936 ImpGetOutputStandard(rNumber, sNum, rNatNum);
4937 sNum.stripStart('-');
4938 sBuff.insert(k, sNum);
4940 break;
4941 case NF_SYMBOLTYPE_FRAC_FDIV: // Do Nothing
4942 if (k > 0)
4944 k--;
4946 break;
4948 default:
4949 if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP )
4950 k = 0; // tdf#100842 jump to beginning of number before inserting something else
4951 sBuff.insert(k, rInfo.sStrArray[j]);
4952 break;
4953 } // of switch
4954 if ( j )
4955 j--; // Next String
4956 else
4957 bStop = true;
4958 } // of while
4959 return bRes;
4962 void SvNumberformat::GetFormatSpecialInfo(bool& bThousand,
4963 bool& IsRed,
4964 sal_uInt16& nPrecision,
4965 sal_uInt16& nLeadingCnt) const
4967 // as before: take info from nNumFor=0 for whole format (for dialog etc.)
4969 SvNumFormatType nDummyType;
4970 GetNumForInfo( 0, nDummyType, bThousand, nPrecision, nLeadingCnt );
4972 // "negative in red" is only useful for the whole format
4974 const Color* pColor = NumFor[1].GetColor();
4975 IsRed = fLimit1 == 0.0 && fLimit2 == 0.0 && pColor
4976 && (*pColor == ImpSvNumberformatScan::GetRedColor());
4979 void SvNumberformat::GetNumForInfo( sal_uInt16 nNumFor, SvNumFormatType& rScannedType,
4980 bool& bThousand, sal_uInt16& nPrecision, sal_uInt16& nLeadingCnt ) const
4982 // take info from a specified sub-format (for XML export)
4984 if ( nNumFor > 3 )
4986 return; // invalid
4989 const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info();
4990 rScannedType = rInfo.eScannedType;
4991 bThousand = rInfo.bThousand;
4992 nPrecision = (rInfo.eScannedType == SvNumFormatType::FRACTION)
4993 ? rInfo.nCntExp // number of denominator digits for fraction
4994 : rInfo.nCntPost;
4995 sal_Int32 nPosHash = 1;
4996 if ( rInfo.eScannedType == SvNumFormatType::FRACTION &&
4997 ( (nPosHash += GetDenominatorString(nNumFor).indexOf('#')) > 0 ) )
4998 nPrecision -= nPosHash;
4999 if (bStandard && rInfo.eScannedType == SvNumFormatType::NUMBER)
5001 // StandardFormat
5002 nLeadingCnt = 1;
5004 else
5006 nLeadingCnt = 0;
5007 bool bStop = false;
5008 sal_uInt16 i = 0;
5009 const sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
5010 while (!bStop && i < nCnt)
5012 short nType = rInfo.nTypeArray[i];
5013 if ( nType == NF_SYMBOLTYPE_DIGIT)
5015 const sal_Unicode* p = rInfo.sStrArray[i].getStr();
5016 while ( *p == '#' )
5018 p++;
5020 while ( *p == '0' || *p == '?' )
5022 nLeadingCnt++;
5023 p++;
5026 else if (nType == NF_SYMBOLTYPE_DECSEP
5027 || nType == NF_SYMBOLTYPE_EXP
5028 || nType == NF_SYMBOLTYPE_FRACBLANK) // Fraction: stop after integer part,
5029 { // do not count '0' of fraction
5030 bStop = true;
5032 i++;
5037 const OUString* SvNumberformat::GetNumForString( sal_uInt16 nNumFor, sal_uInt16 nPos,
5038 bool bString /* = false */ ) const
5040 if ( nNumFor > 3 )
5042 return nullptr;
5044 sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
5045 if ( !nCnt )
5047 return nullptr;
5049 if ( nPos == 0xFFFF )
5051 nPos = nCnt - 1;
5052 if ( bString )
5053 { // Backwards
5054 short const * pType = NumFor[nNumFor].Info().nTypeArray.data() + nPos;
5055 while ( nPos > 0 && (*pType != NF_SYMBOLTYPE_STRING) &&
5056 (*pType != NF_SYMBOLTYPE_CURRENCY) )
5058 pType--;
5059 nPos--;
5061 if ( (*pType != NF_SYMBOLTYPE_STRING) && (*pType != NF_SYMBOLTYPE_CURRENCY) )
5063 return nullptr;
5067 else if ( nPos > nCnt - 1 )
5069 return nullptr;
5071 else if ( bString )
5073 // forward
5074 short const * pType = NumFor[nNumFor].Info().nTypeArray.data() + nPos;
5075 while ( nPos < nCnt && (*pType != NF_SYMBOLTYPE_STRING) &&
5076 (*pType != NF_SYMBOLTYPE_CURRENCY) )
5078 pType++;
5079 nPos++;
5081 if ( nPos >= nCnt || ((*pType != NF_SYMBOLTYPE_STRING) &&
5082 (*pType != NF_SYMBOLTYPE_CURRENCY)) )
5084 return nullptr;
5087 return &NumFor[nNumFor].Info().sStrArray[nPos];
5090 short SvNumberformat::GetNumForType( sal_uInt16 nNumFor, sal_uInt16 nPos ) const
5092 if ( nNumFor > 3 )
5094 return 0;
5096 sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
5097 if ( !nCnt )
5099 return 0;
5101 if ( nPos == 0xFFFF )
5103 nPos = nCnt - 1;
5105 else if ( nPos > nCnt - 1 )
5107 return 0;
5109 return NumFor[nNumFor].Info().nTypeArray[nPos];
5112 bool SvNumberformat::IsNegativeWithoutSign() const
5114 if ( IsSecondSubformatRealNegative() )
5116 const OUString* pStr = GetNumForString( 1, 0, true );
5117 if ( pStr )
5119 return !HasStringNegativeSign( *pStr );
5122 return false;
5125 bool SvNumberformat::IsNegativeInBracket() const
5127 sal_uInt16 nCnt = NumFor[1].GetCount();
5128 if (!nCnt)
5130 return false;
5132 auto& tmp = NumFor[1].Info().sStrArray;
5133 return tmp[0] == "(" && tmp[nCnt-1] == ")";
5136 bool SvNumberformat::HasPositiveBracketPlaceholder() const
5138 sal_uInt16 nCnt = NumFor[0].GetCount();
5139 return NumFor[0].Info().sStrArray[nCnt-1] == "_)";
5142 DateOrder SvNumberformat::GetDateOrder() const
5144 if ( eType & SvNumFormatType::DATE )
5146 auto& rTypeArray = NumFor[0].Info().nTypeArray;
5147 sal_uInt16 nCnt = NumFor[0].GetCount();
5148 for ( sal_uInt16 j=0; j<nCnt; j++ )
5150 switch ( rTypeArray[j] )
5152 case NF_KEY_D :
5153 case NF_KEY_DD :
5154 return DateOrder::DMY;
5155 case NF_KEY_M :
5156 case NF_KEY_MM :
5157 case NF_KEY_MMM :
5158 case NF_KEY_MMMM :
5159 case NF_KEY_MMMMM :
5160 return DateOrder::MDY;
5161 case NF_KEY_YY :
5162 case NF_KEY_YYYY :
5163 case NF_KEY_EC :
5164 case NF_KEY_EEC :
5165 case NF_KEY_R :
5166 case NF_KEY_RR :
5167 return DateOrder::YMD;
5171 else
5173 SAL_WARN( "svl.numbers", "SvNumberformat::GetDateOrder: no date" );
5175 return rLoc().getDateOrder();
5178 sal_uInt32 SvNumberformat::GetExactDateOrder() const
5180 sal_uInt32 nRet = 0;
5181 if ( !(eType & SvNumFormatType::DATE) )
5183 SAL_WARN( "svl.numbers", "SvNumberformat::GetExactDateOrder: no date" );
5184 return nRet;
5186 auto& rTypeArray = NumFor[0].Info().nTypeArray;
5187 sal_uInt16 nCnt = NumFor[0].GetCount();
5188 int nShift = 0;
5189 for ( sal_uInt16 j=0; j<nCnt && nShift < 3; j++ )
5191 switch ( rTypeArray[j] )
5193 case NF_KEY_D :
5194 case NF_KEY_DD :
5195 nRet = (nRet << 8) | 'D';
5196 ++nShift;
5197 break;
5198 case NF_KEY_M :
5199 case NF_KEY_MM :
5200 case NF_KEY_MMM :
5201 case NF_KEY_MMMM :
5202 case NF_KEY_MMMMM :
5203 nRet = (nRet << 8) | 'M';
5204 ++nShift;
5205 break;
5206 case NF_KEY_YY :
5207 case NF_KEY_YYYY :
5208 case NF_KEY_EC :
5209 case NF_KEY_EEC :
5210 case NF_KEY_R :
5211 case NF_KEY_RR :
5212 nRet = (nRet << 8) | 'Y';
5213 ++nShift;
5214 break;
5217 return nRet;
5220 void SvNumberformat::GetConditions( SvNumberformatLimitOps& rOper1, double& rVal1,
5221 SvNumberformatLimitOps& rOper2, double& rVal2 ) const
5223 rOper1 = eOp1;
5224 rOper2 = eOp2;
5225 rVal1 = fLimit1;
5226 rVal2 = fLimit2;
5229 const Color* SvNumberformat::GetColor( sal_uInt16 nNumFor ) const
5231 if ( nNumFor > 3 )
5233 return nullptr;
5235 return NumFor[nNumFor].GetColor();
5238 static void lcl_SvNumberformat_AddLimitStringImpl( OUString& rStr,
5239 SvNumberformatLimitOps eOp,
5240 double fLimit, std::u16string_view rDecSep )
5242 if ( eOp == NUMBERFORMAT_OP_NO )
5243 return;
5245 switch ( eOp )
5247 case NUMBERFORMAT_OP_EQ :
5248 rStr = "[=";
5249 break;
5250 case NUMBERFORMAT_OP_NE :
5251 rStr = "[<>";
5252 break;
5253 case NUMBERFORMAT_OP_LT :
5254 rStr = "[<";
5255 break;
5256 case NUMBERFORMAT_OP_LE :
5257 rStr = "[<=";
5258 break;
5259 case NUMBERFORMAT_OP_GT :
5260 rStr = "[>";
5261 break;
5262 case NUMBERFORMAT_OP_GE :
5263 rStr = "[>=";
5264 break;
5265 default:
5266 SAL_WARN( "svl.numbers", "unsupported number format" );
5267 break;
5269 rStr += ::rtl::math::doubleToUString( fLimit,
5270 rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max,
5271 rDecSep[0], true);
5272 rStr += "]";
5275 static void lcl_insertLCID( OUStringBuffer& rFormatStr, sal_uInt32 nLCID, sal_Int32 nPosInsertLCID, bool bDBNumInserted )
5277 if ( nLCID == 0 )
5278 return;
5279 if (nPosInsertLCID == rFormatStr.getLength() && !bDBNumInserted)
5280 // No format code, no locale.
5281 return;
5283 auto aLCIDString = OUString::number( nLCID , 16 ).toAsciiUpperCase();
5284 // Search for only last DBNum which is the last element before insertion position
5285 if ( bDBNumInserted && nPosInsertLCID >= 8
5286 && aLCIDString.length > 4
5287 && OUString::unacquired(rFormatStr).match( "[DBNum", nPosInsertLCID-8) )
5288 { // remove DBNumX code if long LCID
5289 nPosInsertLCID -= 8;
5290 rFormatStr.remove( nPosInsertLCID, 8 );
5292 rFormatStr.insert( nPosInsertLCID, "[$-" + aLCIDString + "]" );
5295 /** Increment nAlphabetID for CJK numerals
5296 * +1 for financial numerals [NatNum2]
5297 * +2 for Arabic fullwidth numerals [NatNum3]
5298 * */
5299 static void lcl_incrementAlphabetWithNatNum ( sal_uInt32& nAlphabetID, sal_uInt32 nNatNum )
5301 if ( nNatNum == 2) // financial
5302 nAlphabetID += 1;
5303 else if ( nNatNum == 3)
5304 nAlphabetID += 2;
5305 nAlphabetID = nAlphabetID << 24;
5308 OUString SvNumberformat::GetMappedFormatstring( const NfKeywordTable& rKeywords,
5309 const LocaleDataWrapper& rLocWrp,
5310 LanguageType nOriginalLang /* =LANGUAGE_DONTKNOW */,
5311 bool bSystemLanguage /* =false */ ) const
5313 OUStringBuffer aStr;
5314 if (maLocale.meSubstitute != LocaleType::Substitute::NONE)
5316 // XXX: theoretically this could clash with the first subformat's
5317 // lcl_insertLCID() below, in practice as long as it is used for system
5318 // time and date modifiers it shouldn't (i.e. there is no calendar or
5319 // numeral specified as well).
5320 aStr.append("[$-" + maLocale.generateCode() + "]");
5322 bool bDefault[4];
5323 // 1 subformat matches all if no condition specified,
5324 bDefault[0] = ( NumFor[1].GetCount() == 0 && eOp1 == NUMBERFORMAT_OP_NO );
5325 // with 2 subformats [>=0];[<0] is implied if no condition specified
5326 bDefault[1] = ( !bDefault[0] && NumFor[2].GetCount() == 0 &&
5327 eOp1 == NUMBERFORMAT_OP_GE && fLimit1 == 0.0 &&
5328 eOp2 == NUMBERFORMAT_OP_NO && fLimit2 == 0.0 );
5329 // with 3 or more subformats [>0];[<0];[=0] is implied if no condition specified,
5330 // note that subformats may be empty (;;;) and NumFor[2].GetCount()>0 is not checked.
5331 bDefault[2] = ( !bDefault[0] && !bDefault[1] &&
5332 eOp1 == NUMBERFORMAT_OP_GT && fLimit1 == 0.0 &&
5333 eOp2 == NUMBERFORMAT_OP_LT && fLimit2 == 0.0 );
5334 bool bDefaults = bDefault[0] || bDefault[1] || bDefault[2];
5335 // from now on bDefault[] values are used to append empty subformats at the end
5336 bDefault[3] = false;
5337 if ( !bDefaults )
5339 // conditions specified
5340 if ( eOp1 != NUMBERFORMAT_OP_NO && eOp2 == NUMBERFORMAT_OP_NO )
5342 bDefault[0] = bDefault[1] = true; // [];x
5344 else if ( eOp1 != NUMBERFORMAT_OP_NO && eOp2 != NUMBERFORMAT_OP_NO &&
5345 NumFor[2].GetCount() == 0 )
5347 bDefault[0] = bDefault[1] = bDefault[2] = bDefault[3] = true; // [];[];;
5349 // nothing to do if conditions specified for every subformat
5351 else if ( bDefault[0] )
5353 bDefault[0] = false; // a single unconditional subformat is never delimited
5355 else
5357 if ( bDefault[2] && NumFor[2].GetCount() == 0 && NumFor[1].GetCount() > 0 )
5359 bDefault[3] = true; // special cases x;x;; and ;x;;
5361 for ( int i=0; i<3 && !bDefault[i]; ++i )
5363 bDefault[i] = true;
5366 int nSem = 0; // needed ';' delimiters
5367 int nSub = 0; // subformats delimited so far
5368 for ( int n=0; n<4; n++ )
5370 if ( n > 0 && NumFor[n].Info().eScannedType != SvNumFormatType::UNDEFINED )
5372 nSem++;
5374 OUString aPrefix;
5376 if ( !bDefaults )
5378 switch ( n )
5380 case 0 :
5381 lcl_SvNumberformat_AddLimitStringImpl( aPrefix, eOp1,
5382 fLimit1, rLocWrp.getNumDecimalSep() );
5383 break;
5384 case 1 :
5385 lcl_SvNumberformat_AddLimitStringImpl( aPrefix, eOp2,
5386 fLimit2, rLocWrp.getNumDecimalSep() );
5387 break;
5391 const OUString& rColorName = NumFor[n].GetColorName();
5392 if ( !rColorName.isEmpty() )
5394 const NfKeywordTable & rKey = rScan.GetKeywords();
5395 for ( int j = NF_KEY_FIRSTCOLOR; j <= NF_KEY_LASTCOLOR; j++ )
5397 if ( rKey[j] == rColorName )
5399 aPrefix += "[" + rKeywords[j] + "]";
5400 break; // for
5405 SvNumberNatNum aNatNum = NumFor[n].GetNatNum();
5406 bool bDBNumInserted = false;
5407 if (aNatNum.IsComplete() && (aNatNum.GetDBNum() > 0 || nOriginalLang != LANGUAGE_DONTKNOW))
5408 { // GetFormatStringForExcel() may have changed language to en_US
5409 if (aNatNum.GetLang() == LANGUAGE_ENGLISH_US && nOriginalLang != LANGUAGE_DONTKNOW)
5410 aNatNum.SetLang( nOriginalLang );
5411 if ( aNatNum.GetDBNum() > 0 )
5413 aPrefix += "[DBNum" + OUString::number( aNatNum.GetDBNum() ) + "]";
5414 bDBNumInserted = true;
5418 sal_uInt16 nCnt = NumFor[n].GetCount();
5419 if ( nSem && (nCnt || !aPrefix.isEmpty()) )
5421 for ( ; nSem; --nSem )
5423 aStr.append( ';' );
5425 for ( ; nSub <= n; ++nSub )
5427 bDefault[nSub] = false;
5431 if ( !aPrefix.isEmpty() )
5433 aStr.append( aPrefix );
5435 sal_Int32 nPosHaveLCID = -1;
5436 sal_Int32 nPosInsertLCID = aStr.getLength();
5437 sal_uInt32 nCalendarID = 0x0000000; // Excel ID of calendar used in sub-format see tdf#36038
5438 constexpr sal_uInt32 kCalGengou = 0x0030000;
5439 if ( nCnt )
5441 auto& rTypeArray = NumFor[n].Info().nTypeArray;
5442 auto& rStrArray = NumFor[n].Info().sStrArray;
5443 for ( sal_uInt16 j=0; j<nCnt; j++ )
5445 if ( 0 <= rTypeArray[j] && rTypeArray[j] < NF_KEYWORD_ENTRIES_COUNT )
5447 aStr.append( rKeywords[rTypeArray[j]] );
5448 if( NF_KEY_NNNN == rTypeArray[j] )
5450 aStr.append( rLocWrp.getLongDateDayOfWeekSep() );
5452 switch (rTypeArray[j])
5454 case NF_KEY_EC:
5455 case NF_KEY_EEC:
5456 case NF_KEY_R:
5457 case NF_KEY_RR:
5458 // Implicit secondary (non-gregorian) calendar.
5459 // Currently only for ja-JP.
5460 /* TODO: same for all locales in
5461 * LocaleDataWrapper::doesSecondaryCalendarUseEC() ?
5462 * Should split the locales off that then. */
5463 if (!nCalendarID)
5465 const LanguageType nLang = MsLangId::getRealLanguage( nOriginalLang);
5466 if (nLang == LANGUAGE_JAPANESE)
5467 nCalendarID = kCalGengou;
5469 break;
5470 default:
5471 ; // nothing
5474 else
5476 switch ( rTypeArray[j] )
5478 case NF_SYMBOLTYPE_DECSEP :
5479 aStr.append( rLocWrp.getNumDecimalSep() );
5480 break;
5481 case NF_SYMBOLTYPE_THSEP :
5482 aStr.append( rLocWrp.getNumThousandSep() );
5483 break;
5484 case NF_SYMBOLTYPE_EXP :
5485 aStr.append( rKeywords[NF_KEY_E] );
5486 if ( rStrArray[j].getLength() > 1 && rStrArray[j][1] == '+' )
5487 aStr.append( "+" );
5488 else
5489 // tdf#102370: Excel code for exponent without sign
5490 aStr.append( "-" );
5491 break;
5492 case NF_SYMBOLTYPE_DATESEP :
5493 aStr.append( rLocWrp.getDateSep() );
5494 break;
5495 case NF_SYMBOLTYPE_TIMESEP :
5496 aStr.append( rLocWrp.getTimeSep() );
5497 break;
5498 case NF_SYMBOLTYPE_TIME100SECSEP :
5499 aStr.append( rLocWrp.getTime100SecSep() );
5500 break;
5501 case NF_SYMBOLTYPE_FRACBLANK :
5502 case NF_SYMBOLTYPE_STRING :
5503 if ( rStrArray[j].getLength() == 1 )
5505 if ( rTypeArray[j] == NF_SYMBOLTYPE_STRING )
5506 aStr.append( '\\' );
5507 aStr.append( rStrArray[j] );
5509 else
5511 aStr.append( "\"" + rStrArray[j] + "\"" );
5513 break;
5514 case NF_SYMBOLTYPE_CALDEL :
5515 if (j + 1 >= nCnt)
5516 break;
5517 if ( rStrArray[j+1] == "gengou" )
5519 nCalendarID = kCalGengou;
5521 else if ( rStrArray[j+1] == "hijri" )
5523 nCalendarID = 0x0060000;
5525 else if ( rStrArray[j+1] == "buddhist" )
5527 nCalendarID = 0x0070000;
5529 else if ( rStrArray[j+1] == "jewish" )
5531 nCalendarID = 0x0080000;
5533 // Other calendars (see tdf#36038) not corresponding between LibO and XL.
5534 // However, skip any calendar modifier and don't write
5535 // as format code (if not as literal string).
5536 j += 2;
5537 break;
5538 case NF_SYMBOLTYPE_CURREXT :
5539 nPosHaveLCID = aStr.getLength();
5540 aStr.append( rStrArray[j] );
5541 break;
5542 default:
5543 aStr.append( rStrArray[j] );
5548 sal_uInt32 nAlphabetID = 0x0000000; // Excel ID of alphabet used for numerals see tdf#36038
5549 LanguageType nLanguageID = LANGUAGE_SYSTEM;
5550 if ( aNatNum.IsComplete() )
5552 nLanguageID = MsLangId::getRealLanguage( aNatNum.GetLang());
5553 if ( aNatNum.GetNatNum() == 0 )
5555 nAlphabetID = 0x01000000; // Arabic-european numerals
5557 else if ( nCalendarID > 0 || aNatNum.GetDBNum() == 0 || aNatNum.GetDBNum() == aNatNum.GetNatNum() )
5558 { // if no DBNum code then use long LCID
5559 // if DBNum value != NatNum value, use DBNum and not extended LCID
5560 // if calendar, then DBNum will be removed
5561 LanguageType pri = primary(nLanguageID);
5562 if ( pri == LANGUAGE_ARABIC_PRIMARY_ONLY )
5563 nAlphabetID = 0x02000000; // Arabic-indic numerals
5564 else if ( pri == primary(LANGUAGE_FARSI) )
5565 nAlphabetID = 0x03000000; // Farsi numerals
5566 else if ( pri.anyOf(
5567 primary(LANGUAGE_HINDI),
5568 primary(LANGUAGE_MARATHI),
5569 primary(LANGUAGE_NEPALI) ))
5570 nAlphabetID = 0x04000000; // Devanagari numerals
5571 else if ( pri == primary(LANGUAGE_BENGALI) )
5572 nAlphabetID = 0x05000000; // Bengali numerals
5573 else if ( pri == primary(LANGUAGE_PUNJABI) )
5575 if ( nLanguageID == LANGUAGE_PUNJABI_ARABIC_LSO )
5576 nAlphabetID = 0x02000000; // Arabic-indic numerals
5577 else
5578 nAlphabetID = 0x06000000; // Punjabi numerals
5580 else if ( pri == primary(LANGUAGE_GUJARATI) )
5581 nAlphabetID = 0x07000000; // Gujarati numerals
5582 else if ( pri == primary(LANGUAGE_ODIA))
5583 nAlphabetID = 0x08000000; // Odia (Oriya) numerals
5584 else if ( pri == primary(LANGUAGE_TAMIL))
5585 nAlphabetID = 0x09000000; // Tamil numerals
5586 else if ( pri == primary(LANGUAGE_TELUGU))
5587 nAlphabetID = 0x0A000000; // Telugu numerals
5588 else if ( pri == primary(LANGUAGE_KANNADA))
5589 nAlphabetID = 0x0B000000; // Kannada numerals
5590 else if ( pri == primary(LANGUAGE_MALAYALAM))
5591 nAlphabetID = 0x0C000000; // Malayalam numerals
5592 else if ( pri == primary(LANGUAGE_THAI))
5594 // The Thai T NatNum modifier during Xcl export.
5595 if ( rKeywords[NF_KEY_THAI_T] == "T" )
5596 nAlphabetID = 0x0D000000; // Thai numerals
5598 else if ( pri == primary(LANGUAGE_LAO))
5599 nAlphabetID = 0x0E000000; // Lao numerals
5600 else if ( pri == primary(LANGUAGE_TIBETAN))
5601 nAlphabetID = 0x0F000000; // Tibetan numerals
5602 else if ( pri == primary(LANGUAGE_BURMESE))
5603 nAlphabetID = 0x10000000; // Burmese numerals
5604 else if ( pri == primary(LANGUAGE_TIGRIGNA_ETHIOPIA))
5605 nAlphabetID = 0x11000000; // Tigrigna numerals
5606 else if ( pri == primary(LANGUAGE_KHMER))
5607 nAlphabetID = 0x12000000; // Khmer numerals
5608 else if ( pri == primary(LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA))
5610 if ( nLanguageID != LANGUAGE_MONGOLIAN_CYRILLIC_MONGOLIA
5611 && nLanguageID != LANGUAGE_MONGOLIAN_CYRILLIC_LSO )
5612 nAlphabetID = 0x13000000; // Mongolian numerals
5614 // CJK numerals
5615 else if ( pri == primary(LANGUAGE_JAPANESE))
5617 nAlphabetID = 0x1B;
5618 lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() );
5620 else if ( pri == primary(LANGUAGE_CHINESE))
5622 if ( nLanguageID == LANGUAGE_CHINESE_TRADITIONAL
5623 || nLanguageID == LANGUAGE_CHINESE_HONGKONG
5624 || nLanguageID == LANGUAGE_CHINESE_MACAU )
5626 nAlphabetID = 0x21;
5627 lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() );
5629 else // LANGUAGE_CHINESE_SIMPLIFIED
5631 nAlphabetID = 0x1E;
5632 lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() );
5635 else if ( pri == primary(LANGUAGE_KOREAN))
5637 if ( aNatNum.GetNatNum() == 9 ) // Hangul
5639 nAlphabetID = 0x27000000;
5641 else
5643 nAlphabetID = 0x24;
5644 lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() );
5648 // Add LCID to DBNum
5649 if ( aNatNum.GetDBNum() > 0 && nLanguageID == LANGUAGE_SYSTEM )
5650 nLanguageID = MsLangId::getRealLanguage( aNatNum.GetLang());
5652 else if (nPosHaveLCID < 0)
5654 // Do not insert a duplicated LCID that was already given with a
5655 // currency format as [$R-1C09]
5656 if (!bSystemLanguage && nOriginalLang != LANGUAGE_DONTKNOW)
5658 // Explicit locale, write only to the first subformat.
5659 if (n == 0)
5660 nLanguageID = MsLangId::getRealLanguage( nOriginalLang);
5662 else if (bSystemLanguage && maLocale.meLanguageWithoutLocaleData != LANGUAGE_DONTKNOW)
5664 // Explicit locale but no locale data thus assigned to system
5665 // locale, preserve for roundtrip, write only to the first
5666 // subformat.
5667 if (n == 0)
5668 nLanguageID = maLocale.meLanguageWithoutLocaleData;
5671 if ( nCalendarID > 0 )
5672 { // Add alphabet and language to calendar
5673 if ( nAlphabetID == 0 )
5674 nAlphabetID = 0x01000000;
5675 if ( nLanguageID == LANGUAGE_SYSTEM && nOriginalLang != LANGUAGE_DONTKNOW )
5676 nLanguageID = nOriginalLang;
5678 lcl_insertLCID( aStr, nAlphabetID + nCalendarID + static_cast<sal_uInt16>(nLanguageID), nPosInsertLCID,
5679 bDBNumInserted);
5681 for ( ; nSub<4 && bDefault[nSub]; ++nSub )
5682 { // append empty subformats
5683 aStr.append( ';' );
5685 return aStr.makeStringAndClear();
5688 // static
5689 OUString SvNumberformat::ImpGetNatNumString(const SvNumberNatNum& rNum,
5690 sal_Int64 nVal, sal_uInt16 nMinDigits,
5691 const NativeNumberWrapper& rNatNum)
5693 OUString aStr;
5694 if ( nMinDigits )
5696 if ( nMinDigits == 2 )
5698 // speed up the most common case
5699 if ( 0 <= nVal && nVal < 10 )
5701 sal_Unicode aBuf[2];
5702 aBuf[0] = '0';
5703 aBuf[1] = '0' + nVal;
5704 aStr = OUString(aBuf, SAL_N_ELEMENTS(aBuf));
5706 else
5708 aStr = OUString::number( nVal );
5711 else
5713 OUString aValStr( OUString::number( nVal ) );
5714 if ( aValStr.getLength() >= nMinDigits )
5716 aStr = aValStr;
5718 else
5720 OUStringBuffer aBuf;
5721 for(sal_Int32 index = 0; index < nMinDigits - aValStr.getLength(); ++index)
5723 aBuf.append('0');
5725 aBuf.append(aValStr);
5726 aStr = aBuf.makeStringAndClear();
5730 else
5732 aStr = OUString::number( nVal );
5734 return ::impTransliterate(aStr, rNum, rNatNum);
5737 OUString SvNumberformat::impTransliterateImpl(const OUString& rStr,
5738 const SvNumberNatNum& rNum,
5739 const sal_uInt16 nDateKey,
5740 const NativeNumberWrapper& rNatNum) const
5742 // no KEYWORD=argument list in NatNum12
5743 if (rNum.GetParams().indexOf('=') == -1)
5744 return ::impTransliterateImpl( rStr, rNum, rNatNum);
5746 const NfKeywordTable & rKeywords = rScan.GetKeywords();
5748 // Format: KEYWORD=numbertext_prefix, ..., for example:
5749 // [NatNum12 YYYY=title ordinal,MMMM=article, D=ordinal-number]
5750 sal_Int32 nField = -1;
5753 nField = rNum.GetParams().indexOf(Concat2View(rKeywords[nDateKey] + "="), ++nField);
5755 while (nField != -1 && nField != 0 &&
5756 (rNum.GetParams()[nField - 1] != ',' &&
5757 rNum.GetParams()[nField - 1] != ' '));
5759 // no format specified for actual keyword
5760 if (nField == -1)
5761 return rStr;
5763 sal_Int32 nKeywordLen = rKeywords[nDateKey].getLength() + 1;
5764 sal_Int32 nFieldEnd = rNum.GetParams().indexOf(',', nField);
5766 if (nFieldEnd == -1)
5767 nFieldEnd = rNum.GetParams().getLength();
5769 css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() );
5771 return rNatNum.getNativeNumberStringParams(
5772 rStr, aLocale, rNum.GetNatNum(),
5773 rNum.GetParams().copy(nField + nKeywordLen, nFieldEnd - nField - nKeywordLen));
5776 void SvNumberformat::GetNatNumXml( css::i18n::NativeNumberXmlAttributes2& rAttr,
5777 sal_uInt16 nNumFor, const NativeNumberWrapper& rNatNum ) const
5779 if ( nNumFor <= 3 )
5781 const SvNumberNatNum& rNum = NumFor[nNumFor].GetNatNum();
5782 if ( rNum.IsSet() )
5784 css::lang::Locale aLocale(
5785 LanguageTag( rNum.GetLang() ).getLocale() );
5787 /* TODO: a new XNativeNumberSupplier2::convertToXmlAttributes()
5788 * should rather return NativeNumberXmlAttributes2 and places
5789 * adapted, and whether to fill Spellout or something different
5790 * should be internal there. */
5791 css::i18n::NativeNumberXmlAttributes aTmp(
5792 rNatNum.convertToXmlAttributes(
5793 aLocale, rNum.GetNatNum()));
5794 rAttr.Locale = aTmp.Locale;
5795 rAttr.Format = aTmp.Format;
5796 rAttr.Style = aTmp.Style;
5797 if ( NatNumTakesParameters(rNum.GetNatNum()) )
5799 // NatNum12 spell out numbers, dates and money amounts
5800 rAttr.Spellout = rNum.GetParams();
5801 // Mutually exclusive.
5802 rAttr.Format.clear();
5803 rAttr.Style.clear();
5805 else
5807 rAttr.Spellout.clear();
5810 else
5812 rAttr = css::i18n::NativeNumberXmlAttributes2();
5815 else
5817 rAttr = css::i18n::NativeNumberXmlAttributes2();
5821 OUString SvNumberformat::GetNatNumModifierString( sal_uInt16 nNumFor ) const
5823 if ( nNumFor > 3 )
5824 return u""_ustr;
5825 const SvNumberNatNum& rNum = NumFor[nNumFor].GetNatNum();
5826 if ( !rNum.IsSet() )
5827 return u""_ustr;
5828 const sal_Int32 nNum = rNum.GetNatNum();
5829 OUStringBuffer sNatNumModifier = "[NatNum" + OUString::number( nNum );
5830 if ( NatNumTakesParameters( nNum ) )
5832 sNatNumModifier.append( " " + rNum.GetParams() );
5834 sNatNumModifier.append( "]" );
5836 return sNatNumModifier.makeStringAndClear();
5839 // static
5840 bool SvNumberformat::HasStringNegativeSign( const OUString& rStr )
5842 // For Sign '-' needs to be at the start or at the end of the string (blanks ignored)
5843 sal_Int32 nLen = rStr.getLength();
5844 if ( !nLen )
5846 return false;
5848 const sal_Unicode* const pBeg = rStr.getStr();
5849 const sal_Unicode* const pEnd = pBeg + nLen;
5850 const sal_Unicode* p = pBeg;
5852 { // Start
5853 if ( *p == '-' )
5855 return true;
5858 while ( *p == ' ' && ++p < pEnd );
5860 p = pEnd - 1;
5863 { // End
5864 if ( *p == '-' )
5866 return true;
5869 while ( *p == ' ' && pBeg < --p );
5870 return false;
5873 // static
5874 bool SvNumberformat::IsInQuote( const OUString& rStr, sal_Int32 nPos,
5875 sal_Unicode cQuote, sal_Unicode cEscIn, sal_Unicode cEscOut )
5877 sal_Int32 nLen = rStr.getLength();
5878 if ( nPos >= nLen )
5880 return false;
5882 const sal_Unicode* p0 = rStr.getStr();
5883 const sal_Unicode* p = p0;
5884 const sal_Unicode* p1 = p0 + nPos;
5885 bool bQuoted = false;
5886 while ( p <= p1 )
5888 if ( *p == cQuote )
5890 if ( p == p0 )
5892 bQuoted = true;
5894 else if ( bQuoted )
5896 if ( *(p-1) != cEscIn )
5898 bQuoted = false;
5901 else
5903 if ( *(p-1) != cEscOut )
5905 bQuoted = true;
5909 p++;
5911 return bQuoted;
5914 // static
5915 sal_Int32 SvNumberformat::GetQuoteEnd( const OUString& rStr, sal_Int32 nPos,
5916 sal_Unicode cQuote, sal_Unicode cEscIn )
5918 if ( nPos < 0 )
5920 return -1;
5922 sal_Int32 nLen = rStr.getLength();
5923 if ( nPos >= nLen )
5925 return -1;
5927 if ( !IsInQuote( rStr, nPos, cQuote, cEscIn ) )
5929 if ( rStr[ nPos ] == cQuote )
5931 return nPos; // Closing cQuote
5933 return -1;
5935 const sal_Unicode* p0 = rStr.getStr();
5936 const sal_Unicode* p = p0 + nPos;
5937 const sal_Unicode* p1 = p0 + nLen;
5938 while ( p < p1 )
5940 if ( *p == cQuote && p > p0 && *(p-1) != cEscIn )
5942 return sal::static_int_cast< sal_Int32 >(p - p0);
5944 p++;
5946 return nLen; // End of String
5949 sal_uInt16 SvNumberformat::GetNumForNumberElementCount( sal_uInt16 nNumFor ) const
5951 if ( nNumFor < 4 )
5953 sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
5954 return nCnt - ImpGetNumForStringElementCount( nNumFor );
5956 return 0;
5959 sal_uInt16 SvNumberformat::ImpGetNumForStringElementCount( sal_uInt16 nNumFor ) const
5961 sal_uInt16 nCnt = 0;
5962 sal_uInt16 nNumForCnt = NumFor[nNumFor].GetCount();
5963 auto& rTypeArray = NumFor[nNumFor].Info().nTypeArray;
5964 for ( sal_uInt16 j=0; j<nNumForCnt; ++j )
5966 switch ( rTypeArray[j] )
5968 case NF_SYMBOLTYPE_STRING:
5969 case NF_SYMBOLTYPE_CURRENCY:
5970 case NF_SYMBOLTYPE_DATESEP:
5971 case NF_SYMBOLTYPE_TIMESEP:
5972 case NF_SYMBOLTYPE_TIME100SECSEP:
5973 case NF_SYMBOLTYPE_PERCENT:
5974 ++nCnt;
5975 break;
5978 return nCnt;
5981 bool SvNumberformat::IsMinuteSecondFormat() const
5983 if (GetMaskedType() != SvNumFormatType::TIME)
5984 return false;
5986 constexpr sal_uInt16 k00 = 0x00; // Nada, Nilch
5987 constexpr sal_uInt16 kLB = 0x01; // '[' Left Bracket
5988 constexpr sal_uInt16 kRB = 0x02; // ']' Right Bracket
5989 constexpr sal_uInt16 kMM = 0x04; // M or MM
5990 constexpr sal_uInt16 kTS = 0x08; // Time Separator
5991 constexpr sal_uInt16 kSS = 0x10; // S or SS
5992 #define HAS_MINUTE_SECOND(state) ((state) == (kMM|kTS|kSS) || (state) == (kLB|kMM|kRB|kTS|kSS))
5993 // Also (kMM|kTS|kLB|kSS|kRB) but those are the same bits.
5995 sal_uInt16 nState = k00;
5996 bool bSep = false;
5997 sal_uInt16 nNumForCnt = NumFor[0].GetCount();
5998 auto const & rTypeArray = NumFor[0].Info().nTypeArray;
5999 for (sal_uInt16 j=0; j < nNumForCnt; ++j)
6001 switch (rTypeArray[j])
6003 case NF_SYMBOLTYPE_DEL:
6005 // '[' or ']' before/after MM or SS
6006 const OUString& rStr = NumFor[0].Info().sStrArray[j];
6007 if (rStr == "[")
6009 if (nState != k00 && nState != (kMM|kTS))
6010 return false;
6011 nState |= kLB;
6013 else if (rStr == "]")
6015 if (nState != (kLB|kMM) && nState != (kMM|kTS|kLB|kSS))
6016 return false;
6017 nState |= kRB;
6019 else
6020 return false;
6022 break;
6023 case NF_KEY_MI:
6024 case NF_KEY_MMI:
6025 if (nState != k00 && nState != kLB)
6026 return false;
6027 nState |= kMM;
6028 break;
6029 case NF_SYMBOLTYPE_TIMESEP:
6030 if (nState != kMM && nState != (kLB|kMM|kRB))
6031 return false;
6032 nState |= kTS;
6033 break;
6034 case NF_KEY_S:
6035 case NF_KEY_SS:
6036 if (nState != (kMM|kTS) && nState != (kLB|kMM|kRB|kTS) && nState != (kMM|kTS|kLB))
6037 return false;
6038 nState |= kSS;
6039 break;
6040 case NF_SYMBOLTYPE_TIME100SECSEP:
6041 // Trailing fraction of seconds allowed.
6042 if (!HAS_MINUTE_SECOND(nState))
6043 return false;
6044 bSep = true;
6045 break;
6046 case NF_SYMBOLTYPE_DIGIT:
6047 if (!bSep)
6048 return false;
6049 break;
6050 case NF_SYMBOLTYPE_STRING:
6051 // nothing, display literal
6052 break;
6053 default:
6054 return false;
6057 return HAS_MINUTE_SECOND(nState);
6058 #undef HAS_MINUTE_SECOND
6061 OUString SvNumberformat::GetFormatStringForTimePrecision( int nPrecision ) const
6063 OUStringBuffer sString;
6064 using comphelper::string::padToLength;
6066 sal_uInt16 nNumForCnt = NumFor[0].GetCount();
6067 auto const & rTypeArray = NumFor[0].Info().nTypeArray;
6068 for (sal_uInt16 j=0; j < nNumForCnt; ++j)
6070 switch (rTypeArray[j])
6072 case NF_KEY_S :
6073 case NF_KEY_SS:
6074 sString.append( NumFor[0].Info().sStrArray[j] );
6075 if ( j > 0 && rTypeArray[j-1] == NF_SYMBOLTYPE_DEL && j < nNumForCnt-1 )
6077 j++;
6078 sString.append( NumFor[0].Info().sStrArray[j] );
6080 if (nPrecision > 0)
6082 sString.append( rLoc().getTime100SecSep() );
6083 padToLength(sString, sString.getLength() + nPrecision, '0');
6085 break;
6086 case NF_SYMBOLTYPE_TIME100SECSEP:
6087 case NF_SYMBOLTYPE_DIGIT:
6088 break;
6089 case NF_SYMBOLTYPE_STRING:
6090 sString.append( "\"" );
6091 [[fallthrough]];
6092 default:
6093 sString.append( NumFor[0].Info().sStrArray[j] );
6094 if (rTypeArray[j] == NF_SYMBOLTYPE_STRING)
6096 sString.append( "\"" );
6101 return sString.makeStringAndClear();
6104 sal_uInt16 SvNumberformat::GetThousandDivisorPrecision( sal_uInt16 nIx ) const
6106 if (nIx >= 4)
6107 return 0;
6109 const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
6111 if (rInfo.eScannedType != SvNumFormatType::NUMBER && rInfo.eScannedType != SvNumFormatType::CURRENCY)
6112 return 0;
6114 if (rInfo.nThousand == FLAG_STANDARD_IN_FORMAT)
6115 return SvNumberFormatter::UNLIMITED_PRECISION;
6117 return rInfo.nThousand * 3;
6120 const CharClass& SvNumberformat::rChrCls() const
6122 return rScan.GetChrCls();
6125 const LocaleDataWrapper& SvNumberformat::rLoc() const
6127 return rScan.GetLoc();
6130 const SvNFLanguageData& SvNumberformat::GetCurrentLanguageData() const
6132 return rScan.GetCurrentLanguageData();
6135 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */