xref: /core/basic/source/sbx/sbxscan.cxx (revision 47aabde0)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <sal/config.h>
21 
22 #include <string_view>
23 
24 #include <config_features.h>
25 
26 #include <vcl/errcode.hxx>
27 #include <unotools/resmgr.hxx>
28 #include "sbxconv.hxx"
29 #include <rtlproto.hxx>
30 
31 #include <unotools/syslocale.hxx>
32 #include <unotools/charclass.hxx>
33 
34 #include <vcl/svapp.hxx>
35 #include <vcl/settings.hxx>
36 
37 #include <math.h>
38 
39 #include <sbxbase.hxx>
40 #include <sbintern.hxx>
41 #include <sbxform.hxx>
42 
43 #include <date.hxx>
44 #include <runtime.hxx>
45 #include <strings.hrc>
46 
47 #include <rtl/character.hxx>
48 #include <rtl/math.hxx>
49 #include <svl/numformat.hxx>
50 #include <svl/zforlist.hxx>
51 
52 
53 void ImpGetIntntlSep( sal_Unicode& rcDecimalSep, sal_Unicode& rcThousandSep, sal_Unicode& rcDecimalSepAlt )
54 {
55     SvtSysLocale aSysLocale;
56     const LocaleDataWrapper& rData = aSysLocale.GetLocaleData();
57     rcDecimalSep = rData.getNumDecimalSep()[0];
58     rcThousandSep = rData.getNumThousandSep()[0];
59     rcDecimalSepAlt = rData.getNumDecimalSepAlt().toChar();
60 }
61 
62 
63 static bool ImpStrChr( const OUString& str, sal_Unicode c ) { return str.indexOf(c) >= 0; }
64 
65 
66 // scanning a string according to BASIC-conventions
67 // but exponent may also be a D, so data type is SbxDOUBLE
68 // conversion error if data type is fixed and it doesn't fit
69 
70 ErrCode ImpScan( const OUString& rWSrc, double& nVal, SbxDataType& rType,
71                   sal_uInt16* pLen, bool bOnlyIntntl )
72 {
73     sal_Unicode cIntntlDecSep, cIntntlGrpSep, cIntntlDecSepAlt;
74     sal_Unicode cNonIntntlDecSep = '.';
75     if( bOnlyIntntl )
76     {
77         ImpGetIntntlSep( cIntntlDecSep, cIntntlGrpSep, cIntntlDecSepAlt );
78         cNonIntntlDecSep = cIntntlDecSep;
79         // Ensure that the decimal separator alternative is really one.
80         if (cIntntlDecSepAlt && cIntntlDecSepAlt == cNonIntntlDecSep)
81             cIntntlDecSepAlt = 0;
82     }
83     else
84     {
85         cIntntlDecSep = cNonIntntlDecSep;
86         cIntntlGrpSep = 0;  // no group separator accepted in non-i18n
87         cIntntlDecSepAlt = 0;
88     }
89 
90     const sal_Unicode* const pStart = rWSrc.getStr();
91     const sal_Unicode* p = pStart;
92     OUStringBuffer aBuf( rWSrc.getLength());
93     bool bRes = true;
94     bool bMinus = false;
95     nVal = 0;
96     SbxDataType eScanType = SbxSINGLE;
97     while( *p == ' ' || *p == '\t' )
98         p++;
99     if (*p == '+')
100         p++;
101     else if( *p == '-' )
102     {
103         p++;
104         bMinus = true;
105     }
106     if( rtl::isAsciiDigit( *p ) || ((*p == cNonIntntlDecSep || *p == cIntntlDecSep ||
107                     (cIntntlDecSep && *p == cIntntlGrpSep) || (cIntntlDecSepAlt && *p == cIntntlDecSepAlt)) &&
108                 rtl::isAsciiDigit( *(p+1) )))
109     {
110         // tdf#118442: Whitespace and minus are skipped; store the position to calculate index
111         const sal_Unicode* const pDigitsStart = p;
112         short exp = 0;
113         short decsep = 0;
114         short ndig = 0;
115         short ncdig = 0;    // number of digits after decimal point
116         OUStringBuffer aSearchStr("0123456789DEde");
117         aSearchStr.append(cNonIntntlDecSep);
118         if( cIntntlDecSep != cNonIntntlDecSep )
119             aSearchStr.append(cIntntlDecSep);
120         if( cIntntlDecSepAlt && cIntntlDecSepAlt != cNonIntntlDecSep )
121             aSearchStr.append(cIntntlDecSepAlt);
122         if( bOnlyIntntl )
123             aSearchStr.append(cIntntlGrpSep);
124         const OUString pSearchStr = aSearchStr.makeStringAndClear();
125         static const OUStringLiteral pDdEe = u"DdEe";
126         while( ImpStrChr( pSearchStr, *p ) )
127         {
128             aBuf.append( *p );
129             if( bOnlyIntntl && *p == cIntntlGrpSep )
130             {
131                 p++;
132                 continue;
133             }
134             if( *p == cNonIntntlDecSep || *p == cIntntlDecSep || (cIntntlDecSepAlt && *p == cIntntlDecSepAlt) )
135             {
136                 // Use the separator that is passed to stringToDouble()
137                 aBuf[p - pDigitsStart] = cIntntlDecSep;
138                 p++;
139                 if( ++decsep > 1 )
140                     continue;
141             }
142             else if( ImpStrChr( pDdEe, *p ) )
143             {
144                 if( ++exp > 1 )
145                 {
146                     p++;
147                     continue;
148                 }
149                 if( *p == 'D' || *p == 'd' )
150                     eScanType = SbxDOUBLE;
151                 aBuf[p - pDigitsStart] = 'E';
152                 p++;
153                 if (*p == '+')
154                     ++p;
155                 else if (*p == '-')
156                 {
157                     aBuf.append('-');
158                     ++p;
159                 }
160             }
161             else
162             {
163                 p++;
164                 if( decsep && !exp )
165                     ncdig++;
166             }
167             if( !exp )
168                 ndig++;
169         }
170 
171         if( decsep > 1 || exp > 1 )
172             bRes = false;
173 
174         OUString aBufStr( aBuf.makeStringAndClear());
175         rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
176         sal_Int32 nParseEnd = 0;
177         nVal = rtl::math::stringToDouble( aBufStr, cIntntlDecSep, cIntntlGrpSep, &eStatus, &nParseEnd );
178         if( eStatus != rtl_math_ConversionStatus_Ok || nParseEnd != aBufStr.getLength() )
179             bRes = false;
180 
181         if( !decsep && !exp )
182         {
183             if( nVal >= SbxMININT && nVal <= SbxMAXINT )
184                 eScanType = SbxINTEGER;
185             else if( nVal >= SbxMINLNG && nVal <= SbxMAXLNG )
186                 eScanType = SbxLONG;
187         }
188 
189         ndig = ndig - decsep;
190         // too many numbers for SINGLE?
191         if( ndig > 15 || ncdig > 6 )
192             eScanType = SbxDOUBLE;
193 
194         // type detection?
195         static const OUStringLiteral pTypes = u"%!&#";
196         if( ImpStrChr( pTypes, *p ) )
197             p++;
198     }
199     // hex/octal number? read in and convert:
200     else if( *p == '&' )
201     {
202         p++;
203         eScanType = SbxLONG;
204         OUString aCmp( "0123456789ABCDEF" );
205         char base = 16;
206         char ndig = 8;
207         switch( *p++ )
208         {
209             case 'O':
210             case 'o':
211                 aCmp = "01234567";
212                 base = 8;
213                 ndig = 11;
214                 break;
215             case 'H':
216             case 'h':
217                 break;
218             default :
219                 bRes = false;
220         }
221         while( rtl::isAsciiAlphanumeric( *p ) )    /* XXX: really munge all alnum also when error? */
222         {
223             sal_Unicode ch = rtl::toAsciiUpperCase(*p);
224             if( ImpStrChr( aCmp, ch ) )
225                 aBuf.append( ch );
226             else
227                 bRes = false;
228             p++;
229         }
230         OUString aBufStr( aBuf.makeStringAndClear());
231         sal_Int32 l = 0;
232         for( const sal_Unicode* q = aBufStr.getStr(); bRes && *q; q++ )
233         {
234             int i = *q - '0';
235             if( i > 9 )
236                 i -= 7;     // 'A'-'0' = 17 => 10, ...
237             l = ( l * base ) + i;
238             if( !ndig-- )
239                 bRes = false;
240         }
241         if( *p == '&' )
242             p++;
243         nVal = static_cast<double>(l);
244         if( l >= SbxMININT && l <= SbxMAXINT )
245             eScanType = SbxINTEGER;
246     }
247 #if HAVE_FEATURE_SCRIPTING
248     else if ( SbiRuntime::isVBAEnabled() )
249     {
250         return ERRCODE_BASIC_CONVERSION;
251     }
252 #endif
253     if( pLen )
254         *pLen = static_cast<sal_uInt16>( p - pStart );
255     if( !bRes )
256         return ERRCODE_BASIC_CONVERSION;
257     if( bMinus )
258         nVal = -nVal;
259     rType = eScanType;
260     return ERRCODE_NONE;
261 }
262 
263 // port for CDbl in the Basic
264 ErrCode SbxValue::ScanNumIntnl( const OUString& rSrc, double& nVal, bool bSingle )
265 {
266     SbxDataType t;
267     sal_uInt16 nLen = 0;
268     ErrCode nRetError = ImpScan( rSrc, nVal, t, &nLen,
269         /*bOnlyIntntl*/true );
270     // read completely?
271     if( nRetError == ERRCODE_NONE && nLen != rSrc.getLength() )
272     {
273         nRetError = ERRCODE_BASIC_CONVERSION;
274     }
275     if( bSingle )
276     {
277         SbxValues aValues( nVal );
278         nVal = static_cast<double>(ImpGetSingle( &aValues ));    // here error at overflow
279     }
280     return nRetError;
281 }
282 
283 // The number is prepared unformattedly with the given number of
284 // NK-positions. A leading minus is added if applicable.
285 // This routine is public because it's also used by the Put-functions
286 // in the class SbxImpSTRING.
287 
288 void ImpCvtNum( double nNum, short nPrec, OUString& rRes, bool bCoreString )
289 {
290     sal_Unicode cDecimalSep, cThousandSep, cDecimalSepAlt;
291     ImpGetIntntlSep( cDecimalSep, cThousandSep, cDecimalSepAlt );
292     if( bCoreString )
293         cDecimalSep = '.';
294 
295     // tdf#143575 - use rtl::math::doubleToUString to convert numbers to strings in basic
296     rRes = rtl::math::doubleToUString(nNum, rtl_math_StringFormat_Automatic, nPrec, cDecimalSep, true);
297 }
298 
299 bool ImpConvStringExt( OUString& rSrc, SbxDataType eTargetType )
300 {
301     bool bChanged = false;
302     OUString aNewString;
303 
304     // only special cases are handled, nothing on default
305     switch( eTargetType )
306     {
307         // consider international for floating point
308         case SbxSINGLE:
309         case SbxDOUBLE:
310         case SbxCURRENCY:
311         {
312             sal_Unicode cDecimalSep, cThousandSep, cDecimalSepAlt;
313             ImpGetIntntlSep( cDecimalSep, cThousandSep, cDecimalSepAlt );
314             aNewString = rSrc;
315 
316             if( cDecimalSep != '.' || (cDecimalSepAlt && cDecimalSepAlt != '.') )
317             {
318                 sal_Int32 nPos = aNewString.indexOf( cDecimalSep );
319                 if( nPos == -1 && cDecimalSepAlt )
320                     nPos = aNewString.indexOf( cDecimalSepAlt );
321                 if( nPos != -1 )
322                 {
323                     sal_Unicode* pStr = const_cast<sal_Unicode*>(aNewString.getStr());
324                     pStr[nPos] = '.';
325                     bChanged = true;
326                 }
327             }
328             break;
329         }
330 
331         // check as string in case of sal_Bool sal_True and sal_False
332         case SbxBOOL:
333         {
334             if( rSrc.equalsIgnoreAsciiCase("true") )
335             {
336                 aNewString = OUString::number( SbxTRUE );
337                 bChanged = true;
338             }
339             else if( rSrc.equalsIgnoreAsciiCase("false") )
340             {
341                 aNewString = OUString::number( SbxFALSE );
342                 bChanged = true;
343             }
344             break;
345         }
346         default: break;
347     }
348 
349     if( bChanged )
350         rSrc = aNewString;
351     return bChanged;
352 }
353 
354 
355 // formatted number output
356 // the return value is the number of characters used
357 // from the format
358 
359 static sal_uInt16 printfmtstr( const OUString& rStr, OUString& rRes, const OUString& rFmt )
360 {
361     OUStringBuffer aTemp;
362     const sal_Unicode* pStr = rStr.getStr();
363     const sal_Unicode* pFmtStart = rFmt.getStr();
364     const sal_Unicode* pFmt = pFmtStart;
365 
366     switch( *pFmt )
367     {
368     case '!':
369         aTemp.append(*pStr++);
370         pFmt++;
371         break;
372     case '\\':
373         do
374         {
375             aTemp.append( *pStr ? *pStr++ : u' ');
376             pFmt++;
377         }
378         while( *pFmt && *pFmt != '\\' );
379         aTemp.append(*pStr ? *pStr++ : u' ');
380         pFmt++; break;
381     case '&':
382         aTemp = rStr;
383         pFmt++; break;
384     default:
385         aTemp = rStr;
386         break;
387     }
388     rRes = aTemp.makeStringAndClear();
389     return static_cast<sal_uInt16>( pFmt - pFmtStart );
390 }
391 
392 
393 bool SbxValue::Scan( const OUString& rSrc, sal_uInt16* pLen )
394 {
395     ErrCode eRes = ERRCODE_NONE;
396     if( !CanWrite() )
397     {
398         eRes = ERRCODE_BASIC_PROP_READONLY;
399     }
400     else
401     {
402         double n;
403         SbxDataType t;
404         eRes = ImpScan( rSrc, n, t, pLen, !LibreOffice6FloatingPointMode() );
405         if( eRes == ERRCODE_NONE )
406         {
407             if( !IsFixed() )
408             {
409                 SetType( t );
410             }
411             PutDouble( n );
412         }
413     }
414     if( eRes )
415     {
416         SetError( eRes );
417         return false;
418     }
419     else
420     {
421         return true;
422     }
423 }
424 
425 std::locale BasResLocale()
426 {
427     return Translate::Create("sb");
428 }
429 
430 OUString BasResId(TranslateId aId)
431 {
432     return Translate::get(aId, BasResLocale());
433 }
434 
435 namespace
436 {
437 
438 enum class VbaFormatType
439 {
440     Offset,      // standard number format
441     UserDefined, // user defined number format
442     Null
443 };
444 
445 #if HAVE_FEATURE_SCRIPTING
446 
447 struct VbaFormatInfo
448 {
449     VbaFormatType meType;
450     std::u16string_view mpVbaFormat; // Format string in vba
451     NfIndexTableOffset meOffset; // SvNumberFormatter format index, if meType = VbaFormatType::Offset
452     const char* mpOOoFormat;     // if meType = VbaFormatType::UserDefined
453 };
454 
455 const VbaFormatInfo pFormatInfoTable[] =
456 {
457     { VbaFormatType::Offset,      std::u16string_view(u"Long Date"),   NF_DATE_SYSTEM_LONG,    nullptr },
458     { VbaFormatType::UserDefined, std::u16string_view(u"Medium Date"), NF_NUMBER_STANDARD,     "DD-MMM-YY" },
459     { VbaFormatType::Offset,      std::u16string_view(u"Short Date"),  NF_DATE_SYSTEM_SHORT,   nullptr },
460     { VbaFormatType::UserDefined, std::u16string_view(u"Long Time"),   NF_NUMBER_STANDARD,     "H:MM:SS AM/PM" },
461     { VbaFormatType::Offset,      std::u16string_view(u"Medium Time"), NF_TIME_HHMMAMPM,       nullptr },
462     { VbaFormatType::Offset,      std::u16string_view(u"Short Time"),  NF_TIME_HHMM,           nullptr },
463     { VbaFormatType::Offset,      std::u16string_view(u"ddddd"),       NF_DATE_SYSTEM_SHORT,   nullptr },
464     { VbaFormatType::Offset,      std::u16string_view(u"dddddd"),      NF_DATE_SYSTEM_LONG,    nullptr },
465     { VbaFormatType::UserDefined, std::u16string_view(u"ttttt"),       NF_NUMBER_STANDARD,     "H:MM:SS AM/PM" },
466     { VbaFormatType::Offset,      std::u16string_view(u"ww"),          NF_DATE_WW,             nullptr },
467     { VbaFormatType::Null,        std::u16string_view(u""),            NF_INDEX_TABLE_ENTRIES, nullptr }
468 };
469 
470 const VbaFormatInfo* getFormatInfo( const OUString& rFmt )
471 {
472     const VbaFormatInfo* pInfo = pFormatInfoTable;
473     while( pInfo->meType != VbaFormatType::Null )
474     {
475         if( rFmt.equalsIgnoreAsciiCase( pInfo->mpVbaFormat ) )
476             break;
477         ++pInfo;
478     }
479     return pInfo;
480 }
481 #endif
482 
483 } // namespace
484 
485 #if HAVE_FEATURE_SCRIPTING
486 constexpr OUStringLiteral VBAFORMAT_GENERALDATE = u"General Date";
487 constexpr OUStringLiteral VBAFORMAT_C = u"c";
488 constexpr OUStringLiteral VBAFORMAT_N = u"n";
489 constexpr OUStringLiteral VBAFORMAT_NN = u"nn";
490 constexpr OUStringLiteral VBAFORMAT_W = u"w";
491 constexpr OUStringLiteral VBAFORMAT_Y = u"y";
492 constexpr OUStringLiteral VBAFORMAT_LOWERCASE = u"<";
493 constexpr OUStringLiteral VBAFORMAT_UPPERCASE = u">";
494 #endif
495 
496 void SbxValue::Format( OUString& rRes, const OUString* pFmt ) const
497 {
498     short nComma = 0;
499     double d = 0;
500 
501     // pflin, It is better to use SvNumberFormatter to handle the date/time/number format.
502     // the SvNumberFormatter output is mostly compatible with
503     // VBA output besides the OOo-basic output
504 #if HAVE_FEATURE_SCRIPTING
505     if( pFmt && !SbxBasicFormater::isBasicFormat( *pFmt ) )
506     {
507         OUString aStr = GetOUString();
508 
509         SvtSysLocale aSysLocale;
510         const CharClass& rCharClass = aSysLocale.GetCharClass();
511 
512         if( pFmt->equalsIgnoreAsciiCase( VBAFORMAT_LOWERCASE ) )
513         {
514             rRes = rCharClass.lowercase( aStr );
515             return;
516         }
517         if( pFmt->equalsIgnoreAsciiCase( VBAFORMAT_UPPERCASE ) )
518         {
519             rRes = rCharClass.uppercase( aStr );
520             return;
521         }
522 
523         LanguageType eLangType = Application::GetSettings().GetLanguageTag().getLanguageType();
524         std::shared_ptr<SvNumberFormatter> pFormatter;
525         if (GetSbData()->pInst)
526         {
527             pFormatter = GetSbData()->pInst->GetNumberFormatter();
528         }
529         else
530         {
531             sal_uInt32 n;   // Dummy
532             pFormatter = SbiInstance::PrepareNumberFormatter( n, n, n );
533         }
534 
535         // Passing an index of a locale switches IsNumberFormat() to use that
536         // locale in case the formatter wasn't default created with it.
537         sal_uInt32 nIndex = pFormatter->GetStandardIndex( eLangType);
538         double nNumber;
539         const Color* pCol;
540 
541         bool bSuccess = pFormatter->IsNumberFormat( aStr, nIndex, nNumber );
542 
543         // number format, use SvNumberFormatter to handle it.
544         if( bSuccess )
545         {
546             sal_Int32 nCheckPos = 0;
547             SvNumFormatType nType;
548             OUString aFmtStr = *pFmt;
549             const VbaFormatInfo* pInfo = getFormatInfo( aFmtStr );
550             if( pInfo->meType != VbaFormatType::Null )
551             {
552                 if( pInfo->meType == VbaFormatType::Offset )
553                 {
554                     nIndex = pFormatter->GetFormatIndex( pInfo->meOffset, eLangType );
555                 }
556                 else
557                 {
558                     aFmtStr = OUString::createFromAscii(pInfo->mpOOoFormat);
559                     pFormatter->PutandConvertEntry( aFmtStr, nCheckPos, nType, nIndex, LANGUAGE_ENGLISH_US, eLangType, true);
560                 }
561                 pFormatter->GetOutputString( nNumber, nIndex, rRes, &pCol );
562             }
563             else if( aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_GENERALDATE )
564                     || aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_C ))
565             {
566                 if( nNumber <=-1.0 || nNumber >= 1.0 )
567                 {
568                     // short date
569                     nIndex = pFormatter->GetFormatIndex( NF_DATE_SYSTEM_SHORT, eLangType );
570                     pFormatter->GetOutputString( nNumber, nIndex, rRes, &pCol );
571 
572                     // long time
573                     if( floor( nNumber ) != nNumber )
574                     {
575                         aFmtStr = "H:MM:SS AM/PM";
576                         pFormatter->PutandConvertEntry( aFmtStr, nCheckPos, nType, nIndex, LANGUAGE_ENGLISH_US, eLangType, true);
577                         OUString aTime;
578                         pFormatter->GetOutputString( nNumber, nIndex, aTime, &pCol );
579                         rRes += " " + aTime;
580                     }
581                 }
582                 else
583                 {
584                     // long time only
585                     aFmtStr = "H:MM:SS AM/PM";
586                     pFormatter->PutandConvertEntry( aFmtStr, nCheckPos, nType, nIndex, LANGUAGE_ENGLISH_US, eLangType, true);
587                     pFormatter->GetOutputString( nNumber, nIndex, rRes, &pCol );
588                 }
589             }
590             else if( aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_N ) ||
591                      aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_NN ))
592             {
593                 sal_Int32 nMin = implGetMinute( nNumber );
594                 if( nMin < 10 && aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_NN ))
595                 {
596                     // Minute in two digits
597                      sal_Unicode aBuf[2];
598                      aBuf[0] = '0';
599                      aBuf[1] = '0' + nMin;
600                      rRes = OUString(aBuf, SAL_N_ELEMENTS(aBuf));
601                 }
602                 else
603                 {
604                     rRes = OUString::number(nMin);
605                 }
606             }
607             else if( aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_W ))
608             {
609                 sal_Int32 nWeekDay = implGetWeekDay( nNumber );
610                 rRes = OUString::number(nWeekDay);
611             }
612             else if( aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_Y ))
613             {
614                 sal_Int16 nYear = implGetDateYear( nNumber );
615                 double dBaseDate;
616                 implDateSerial( nYear, 1, 1, true, SbDateCorrection::None, dBaseDate );
617                 sal_Int32 nYear32 = 1 + sal_Int32( nNumber - dBaseDate );
618                 rRes = OUString::number(nYear32);
619             }
620             else
621             {
622                 pFormatter->PutandConvertEntry( aFmtStr, nCheckPos, nType, nIndex, LANGUAGE_ENGLISH_US, eLangType, true);
623                 pFormatter->GetOutputString( nNumber, nIndex, rRes, &pCol );
624             }
625 
626             return;
627         }
628     }
629 #endif
630 
631     SbxDataType eType = GetType();
632     switch( eType )
633     {
634     case SbxCHAR:
635     case SbxBYTE:
636     case SbxINTEGER:
637     case SbxUSHORT:
638     case SbxLONG:
639     case SbxULONG:
640     case SbxINT:
641     case SbxUINT:
642     case SbxNULL:       // #45929 NULL with a little cheating
643         nComma = 0;     goto cvt;
644     case SbxSINGLE:
645         nComma = 6;     goto cvt;
646     case SbxDOUBLE:
647         nComma = 14;
648 
649     cvt:
650         if( eType != SbxNULL )
651         {
652             d = GetDouble();
653         }
654         // #45355 another point to jump in for isnumeric-String
655     cvt2:
656         if( pFmt )
657         {
658             SbxAppData& rAppData = GetSbxData_Impl();
659 
660             LanguageType eLangType = Application::GetSettings().GetLanguageTag().getLanguageType();
661             if( rAppData.pBasicFormater )
662             {
663                 if( rAppData.eBasicFormaterLangType != eLangType )
664                 {
665                     rAppData.pBasicFormater.reset();
666                 }
667             }
668             rAppData.eBasicFormaterLangType = eLangType;
669 
670 
671             if( !rAppData.pBasicFormater )
672             {
673                 SvtSysLocale aSysLocale;
674                 const LocaleDataWrapper& rData = aSysLocale.GetLocaleData();
675                 sal_Unicode cComma = rData.getNumDecimalSep()[0];
676                 sal_Unicode c1000  = rData.getNumThousandSep()[0];
677                 const OUString& aCurrencyStrg = rData.getCurrSymbol();
678 
679                 // initialize the Basic-formater help object:
680                 // get resources for predefined output
681                 // of the Format()-command, e. g. for "On/Off"
682                 OUString aOnStrg = BasResId(STR_BASICKEY_FORMAT_ON);
683                 OUString aOffStrg = BasResId(STR_BASICKEY_FORMAT_OFF);
684                 OUString aYesStrg = BasResId(STR_BASICKEY_FORMAT_YES);
685                 OUString aNoStrg = BasResId(STR_BASICKEY_FORMAT_NO);
686                 OUString aTrueStrg = BasResId(STR_BASICKEY_FORMAT_TRUE);
687                 OUString aFalseStrg = BasResId(STR_BASICKEY_FORMAT_FALSE);
688                 OUString aCurrencyFormatStrg = BasResId(STR_BASICKEY_FORMAT_CURRENCY);
689 
690                 rAppData.pBasicFormater = std::make_unique<SbxBasicFormater>(
691                                                                 cComma,c1000,aOnStrg,aOffStrg,
692                                                                 aYesStrg,aNoStrg,aTrueStrg,aFalseStrg,
693                                                                 aCurrencyStrg,aCurrencyFormatStrg );
694             }
695             // Remark: For performance reasons there's only ONE BasicFormater-
696             //    object created and 'stored', so that the expensive resource-
697             //    loading is saved (for country-specific predefined outputs,
698             //    e. g. "On/Off") and the continuous string-creation
699             //    operations, too.
700             // BUT: therefore this code is NOT multithreading capable!
701 
702             // here are problems with ;;;Null because this method is only
703             // called, if SbxValue is a number!!!
704             // in addition rAppData.pBasicFormater->BasicFormatNull( *pFmt ); could be called!
705             if( eType != SbxNULL )
706             {
707                 rRes = rAppData.pBasicFormater->BasicFormat( d ,*pFmt );
708             }
709             else
710             {
711                 rRes = SbxBasicFormater::BasicFormatNull( *pFmt );
712             }
713 
714         }
715         else
716             ImpCvtNum( GetDouble(), nComma, rRes );
717         break;
718     case SbxSTRING:
719         if( pFmt )
720         {
721             // #45355 converting if numeric
722             if( IsNumericRTL() )
723             {
724                 ScanNumIntnl( GetOUString(), d );
725                 goto cvt2;
726             }
727             else
728             {
729                 printfmtstr( GetOUString(), rRes, *pFmt );
730             }
731         }
732         else
733         {
734             rRes = GetOUString();
735         }
736         break;
737     default:
738         rRes = GetOUString();
739     }
740 }
741 
742 
743 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
744