xref: /trunk/main/basic/source/sbx/sbxscan.cxx (revision a3cdc23e488c57f3433f22cd4458e65c27aa499c)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 // MARKER(update_precomp.py): autogen include statement, do not remove
23 #include "precompiled_basic.hxx"
24 #include <tools/errcode.hxx>
25 #include <basic/sbx.hxx>
26 #include "sbxconv.hxx"
27 
28 #include "unotools/syslocale.hxx"
29 
30 #if defined ( UNX )
31 #include <stdlib.h>
32 #endif
33 
34 #ifndef _APP_HXX //autogen
35 #include <vcl/svapp.hxx>
36 #endif
37 #include <math.h>
38 #include <string.h>
39 #include <ctype.h>
40 
41 #include "sbxres.hxx"
42 #include <basic/sbxbase.hxx>
43 #include <basic/sbxform.hxx>
44 #include <svtools/svtools.hrc>
45 
46 #include "basrid.hxx"
47 #include "runtime.hxx"
48 
49 #include <svl/zforlist.hxx>
50 #include <comphelper/processfactory.hxx>
51 
52 
53 void ImpGetIntntlSep( sal_Unicode& rcDecimalSep, sal_Unicode& rcThousandSep )
54 {
55     SvtSysLocale aSysLocale;
56     const LocaleDataWrapper& rData = aSysLocale.GetLocaleData();
57     rcDecimalSep = rData.getNumDecimalSep().GetBuffer()[0];
58     rcThousandSep = rData.getNumThousandSep().GetBuffer()[0];
59 }
60 
61 // Scannen eines Strings nach BASIC-Konventionen
62 // Dies entspricht den üblichen Konventionen, nur dass der Exponent
63 // auch ein D sein darf, was den Datentyp auf SbxDOUBLE festlegt.
64 // Die Routine versucht, den Datentyp so klein wie möglich zu gestalten.
65 // Das ganze gibt auch noch einen Konversionsfehler, wenn der Datentyp
66 // Fixed ist und das ganze nicht hineinpasst!
67 
68 SbxError ImpScan( const ::rtl::OUString& rWSrc, double& nVal, SbxDataType& rType,
69                   sal_uInt16* pLen, sal_Bool bAllowIntntl, sal_Bool bOnlyIntntl )
70 {
71     ::rtl::OString aBStr( ::rtl::OUStringToOString( rWSrc, RTL_TEXTENCODING_ASCII_US ) );
72 
73     // Bei International Komma besorgen
74     char cIntntlComma, cIntntl1000;
75     char cNonIntntlComma = '.';
76 
77     sal_Unicode cDecimalSep, cThousandSep = 0;
78     if( bAllowIntntl || bOnlyIntntl )
79     {
80         ImpGetIntntlSep( cDecimalSep, cThousandSep );
81         cIntntlComma = (char)cDecimalSep;
82         cIntntl1000 = (char)cThousandSep;
83     }
84     // Sonst einfach auch auf . setzen
85     else
86     {
87         cIntntlComma = cNonIntntlComma;
88         cIntntl1000 = cNonIntntlComma;  // Unschädlich machen
89     }
90     // Nur International -> IntnlComma übernehmen
91     if( bOnlyIntntl )
92     {
93         cNonIntntlComma = cIntntlComma;
94         cIntntl1000 = (char)cThousandSep;
95     }
96 
97     const char* pStart = aBStr.getStr();
98     const char* p = pStart;
99     char buf[ 80 ], *q = buf;
100     sal_Bool bRes = sal_True;
101     sal_Bool bMinus = sal_False;
102     nVal = 0;
103     SbxDataType eScanType = SbxSINGLE;
104     // Whitespace wech
105     while( *p &&( *p == ' ' || *p == '\t' ) ) p++;
106     // Zahl? Dann einlesen und konvertieren.
107     if( *p == '-' )
108         p++, bMinus = sal_True;
109     if( isdigit( *p ) ||( (*p == cNonIntntlComma || *p == cIntntlComma ||
110             *p == cIntntl1000) && isdigit( *(p+1 ) ) ) )
111     {
112         short exp = 0;      // >0: Exponentteil
113         short comma = 0;    // >0: Nachkomma
114         short ndig = 0;     // Anzahl Ziffern
115         short ncdig = 0;    // Anzahl Ziffern nach Komma
116         ByteString aSearchStr( "0123456789DEde" );
117         // Kommas ergaenzen
118         aSearchStr += cNonIntntlComma;
119         if( cIntntlComma != cNonIntntlComma )
120             aSearchStr += cIntntlComma;
121         if( bOnlyIntntl )
122             aSearchStr += cIntntl1000;
123         const char* pSearchStr = aSearchStr.GetBuffer();
124         while( strchr( pSearchStr, *p ) && *p )
125         {
126             // 1000er-Trenner überlesen
127             if( bOnlyIntntl && *p == cIntntl1000 )
128             {
129                 p++;
130                 continue;
131             }
132 
133             // Komma oder Exponent?
134             if( *p == cNonIntntlComma || *p == cIntntlComma )
135             {
136                 // Immer '.' einfügen, damit atof funktioniert
137                 p++;
138                 if( ++comma > 1 )
139                     continue;
140                 else
141                     *q++ = '.';
142             }
143             else if( strchr( "DdEe", *p ) )
144             {
145                 if( ++exp > 1 )
146                 {
147                     p++; continue;
148                 }
149                 if( toupper( *p ) == 'D' )
150                     eScanType = SbxDOUBLE;
151                 *q++ = 'E'; p++;
152                 // Vorzeichen hinter Exponent?
153                 if( *p == '+' )
154                     p++;
155                 else
156                 if( *p == '-' )
157                     *q++ = *p++;
158             }
159             else
160             {
161                 *q++ = *p++;
162                 if( comma && !exp ) ncdig++;
163             }
164             if( !exp ) ndig++;
165         }
166         *q = 0;
167         // Komma, Exponent mehrfach vorhanden?
168         if( comma > 1 || exp > 1 )
169             bRes = sal_False;
170         // Kann auf Integer gefaltet werden?
171         if( !comma && !exp )
172         {
173             if( nVal >= SbxMININT && nVal <= SbxMAXINT )
174                 eScanType = SbxINTEGER;
175             else if( nVal >= SbxMINLNG && nVal <= SbxMAXLNG )
176                 eScanType = SbxLONG;
177         }
178 
179         nVal = atof( buf );
180         ndig = ndig - comma;
181         // zu viele Zahlen für SINGLE?
182         if( ndig > 15 || ncdig > 6 )
183             eScanType = SbxDOUBLE;
184 
185         // Typkennung?
186         if( strchr( "%!&#", *p ) && *p ) p++;
187     }
188     // Hex/Oktalzahl? Einlesen und konvertieren:
189     else if( *p == '&' )
190     {
191         p++;
192         eScanType = SbxLONG;
193         const char *cmp = "0123456789ABCDEF";
194         char base = 16;
195         char ndig = 8;
196         char xch  = *p++;
197         switch( toupper( xch ) )
198         {
199             case 'O': cmp = "01234567"; base = 8; ndig = 11; break;
200             case 'H': break;
201             default : bRes = sal_False;
202         }
203         sal_Int32 l = 0;
204         int i;
205         while( isalnum( *p ) )
206         {
207             char ch = sal::static_int_cast< char >( toupper( *p ) );
208             p++;
209             if( strchr( cmp, ch ) ) *q++ = ch;
210             else bRes = sal_False;
211         }
212         *q = 0;
213         for( q = buf; *q; q++ )
214         {
215             i =( *q & 0xFF ) - '0';
216             if( i > 9 ) i -= 7;
217             l =( l * base ) + i;
218             if( !ndig-- )
219                 bRes = sal_False;
220         }
221         if( *p == '&' ) p++;
222         nVal = (double) l;
223         if( l >= SbxMININT && l <= SbxMAXINT )
224             eScanType = SbxINTEGER;
225     }
226     else if ( SbiRuntime::isVBAEnabled() )
227     {
228         OSL_TRACE("Reporting error converting");
229         return SbxERR_CONVERSION;
230     }
231     if( pLen )
232         *pLen = (sal_uInt16) ( p - pStart );
233     if( !bRes )
234         return SbxERR_CONVERSION;
235     if( bMinus )
236         nVal = -nVal;
237     rType = eScanType;
238     return SbxERR_OK;
239 }
240 
241 // Schnittstelle für CDbl im Basic
242 SbxError SbxValue::ScanNumIntnl( const String& rSrc, double& nVal, sal_Bool bSingle )
243 {
244     SbxDataType t;
245     sal_uInt16 nLen = 0;
246     SbxError nRetError = ImpScan( rSrc, nVal, t, &nLen,
247         /*bAllowIntntl*/sal_False, /*bOnlyIntntl*/sal_True );
248     // Komplett gelesen?
249     if( nRetError == SbxERR_OK && nLen != rSrc.Len() )
250         nRetError = SbxERR_CONVERSION;
251 
252     if( bSingle )
253     {
254         SbxValues aValues( nVal );
255         nVal = (double)ImpGetSingle( &aValues );    // Hier Error bei Overflow
256     }
257     return nRetError;
258 }
259 
260 ////////////////////////////////////////////////////////////////////////////
261 
262 static double roundArray[] = {
263     5.0e+0, 0.5e+0, 0.5e-1, 0.5e-2, 0.5e-3, 0.5e-4, 0.5e-5, 0.5e-6, 0.5e-7,
264     0.5e-8, 0.5e-9, 0.5e-10,0.5e-11,0.5e-12,0.5e-13,0.5e-14,0.5e-15 };
265 
266 /***************************************************************************
267 |*
268 |*  void myftoa( double, char *, short, short, sal_Bool, sal_Bool )
269 |*
270 |*  Beschreibung:       Konversion double --> ASCII
271 |*  Parameter:          double              die Zahl.
272 |*                      char *              der Zielpuffer
273 |*                      short               Anzahl Nachkommastellen
274 |*                      short               Weite des Exponenten( 0=kein E )
275 |*                      sal_Bool                sal_True: mit 1000er Punkten
276 |*                      sal_Bool                sal_True: formatfreie Ausgabe
277 |*
278 ***************************************************************************/
279 
280 static void myftoa( double nNum, char * pBuf, short nPrec, short nExpWidth,
281                     sal_Bool bPt, sal_Bool bFix, sal_Unicode cForceThousandSep = 0 )
282 {
283 
284     short nExp = 0;                     // Exponent
285     short nDig = nPrec + 1;             // Anzahl Digits in Zahl
286     short nDec;                         // Anzahl Vorkommastellen
287     register int i, digit;
288 
289     // Komma besorgen
290     sal_Unicode cDecimalSep, cThousandSep;
291     ImpGetIntntlSep( cDecimalSep, cThousandSep );
292     if( cForceThousandSep )
293         cThousandSep = cForceThousandSep;
294 
295     // Exponentberechnung:
296     nExp = 0;
297     if( nNum > 0.0 )
298     {
299         while( nNum <   1.0 ) nNum *= 10.0, nExp--;
300         while( nNum >= 10.0 ) nNum /= 10.0, nExp++;
301     }
302     if( !bFix && !nExpWidth )
303         nDig = nDig + nExp;
304     else if( bFix && !nPrec )
305         nDig = nExp + 1;
306 
307     // Zahl runden:
308     if( (nNum += roundArray [( nDig > 16 ) ? 16 : nDig] ) >= 10.0 )
309     {
310         nNum = 1.0;
311         ++nExp;
312         if( !nExpWidth ) ++nDig;
313     }
314 
315     // Bestimmung der Vorkommastellen:
316     if( !nExpWidth )
317     {
318         if( nExp < 0 )
319         {
320             // #41691: Auch bei bFix eine 0 spendieren
321             *pBuf++ = '0';
322             if( nPrec ) *pBuf++ = (char)cDecimalSep;
323             i = -nExp - 1;
324             if( nDig <= 0 ) i = nPrec;
325             while( i-- )    *pBuf++ = '0';
326             nDec = 0;
327         }
328         else
329             nDec = nExp+1;
330     }
331     else
332         nDec = 1;
333 
334     // Zahl ausgeben:
335     if( nDig > 0 )
336     {
337         for( i = 0 ; ; ++i )
338         {
339             if( i < 16 )
340             {
341                 digit = (int) nNum;
342                 *pBuf++ = sal::static_int_cast< char >(digit + '0');
343                 nNum =( nNum - digit ) * 10.0;
344             } else
345                 *pBuf++ = '0';
346             if( --nDig == 0 ) break;
347             if( nDec )
348             {
349                 nDec--;
350                 if( !nDec )
351                     *pBuf++ = (char)cDecimalSep;
352                 else if( !(nDec % 3 ) && bPt )
353                     *pBuf++ = (char)cThousandSep;
354             }
355         }
356     }
357 
358     // Exponent ausgeben:
359     if( nExpWidth )
360     {
361         if( nExpWidth < 3 ) nExpWidth = 3;
362         nExpWidth -= 2;
363         *pBuf++ = 'E';
364         *pBuf++ =( nExp < 0 ) ?( (nExp = -nExp ), '-' ) : '+';
365         while( nExpWidth > 3 ) *pBuf++ = '0', nExpWidth--;
366         if( nExp >= 100 || nExpWidth == 3 )
367         {
368             *pBuf++ = sal::static_int_cast< char >(nExp/100 + '0');
369             nExp %= 100;
370         }
371         if( nExp/10 || nExpWidth >= 2 )
372             *pBuf++ = sal::static_int_cast< char >(nExp/10 + '0');
373         *pBuf++ = sal::static_int_cast< char >(nExp%10 + '0');
374     }
375     *pBuf = 0;
376 }
377 
378 // Die Zahl wird unformatiert mit der angegebenen Anzahl NK-Stellen
379 // aufbereitet. Evtl. wird ein Minus vorangestellt.
380 // Diese Routine ist public, weil sie auch von den Put-Funktionen
381 // der Klasse SbxImpSTRING verwendet wird.
382 
383 #ifdef _MSC_VER
384 #pragma optimize( "", off )
385 #pragma warning(disable: 4748) // "... because optimizations are disabled ..."
386 #endif
387 
388 void ImpCvtNum( double nNum, short nPrec, ::rtl::OUString& rRes, sal_Bool bCoreString )
389 {
390     char *q;
391     char cBuf[ 40 ], *p = cBuf;
392 
393     sal_Unicode cDecimalSep, cThousandSep;
394     ImpGetIntntlSep( cDecimalSep, cThousandSep );
395     if( bCoreString )
396         cDecimalSep = '.';
397 
398     if( nNum < 0.0 ) {
399         nNum = -nNum;
400         *p++ = '-';
401     }
402     double dMaxNumWithoutExp = (nPrec == 6) ? 1E6 : 1E14;
403     myftoa( nNum, p, nPrec,( nNum &&( nNum < 1E-1 || nNum >= dMaxNumWithoutExp ) ) ? 4:0,
404         sal_False, sal_True, cDecimalSep );
405     // Trailing Zeroes weg:
406     for( p = cBuf; *p &&( *p != 'E' ); p++ ) {}
407     q = p; p--;
408     while( nPrec && *p == '0' ) nPrec--, p--;
409     if( *p == cDecimalSep ) p--;
410     while( *q ) *++p = *q++;
411     *++p = 0;
412     rRes = ::rtl::OUString::createFromAscii( cBuf );
413 }
414 
415 #ifdef _MSC_VER
416 #pragma optimize( "", on )
417 #endif
418 
419 sal_Bool ImpConvStringExt( ::rtl::OUString& rSrc, SbxDataType eTargetType )
420 {
421     // Merken, ob überhaupt was geändert wurde
422     sal_Bool bChanged = sal_False;
423     ::rtl::OUString aNewString;
424 
425     // Nur Spezial-Fälle behandeln, als Default tun wir nichts
426     switch( eTargetType )
427     {
428         // Bei Fliesskomma International berücksichtigen
429         case SbxSINGLE:
430         case SbxDOUBLE:
431         case SbxCURRENCY:
432         {
433             ::rtl::OString aBStr( ::rtl::OUStringToOString( rSrc, RTL_TEXTENCODING_ASCII_US ) );
434 
435             // Komma besorgen
436             sal_Unicode cDecimalSep, cThousandSep;
437             ImpGetIntntlSep( cDecimalSep, cThousandSep );
438             aNewString = rSrc;
439 
440             // Ersetzen, wenn DecimalSep kein '.' (nur den ersten)
441             if( cDecimalSep != (sal_Unicode)'.' )
442             {
443                 sal_Int32 nPos = aNewString.indexOf( cDecimalSep );
444                 if( nPos != -1 )
445                 {
446                     sal_Unicode* pStr = (sal_Unicode*)aNewString.getStr();
447                     pStr[nPos] = (sal_Unicode)'.';
448                     bChanged = sal_True;
449                 }
450             }
451             break;
452         }
453 
454         // Bei sal_Bool sal_True und sal_False als String prüfen
455         case SbxBOOL:
456         {
457             if( rSrc.equalsIgnoreAsciiCaseAscii( "true" ) )
458             {
459                 aNewString = ::rtl::OUString::valueOf( (sal_Int32)SbxTRUE );
460                 bChanged = sal_True;
461             }
462             else
463             if( rSrc.equalsIgnoreAsciiCaseAscii( "false" ) )
464             {
465                 aNewString = ::rtl::OUString::valueOf( (sal_Int32)SbxFALSE );
466                 bChanged = sal_True;
467             }
468             break;
469         }
470         default: break;
471     }
472     // String bei Änderung übernehmen
473     if( bChanged )
474         rSrc = aNewString;
475     return bChanged;
476 }
477 
478 
479 // Formatierte Zahlenausgabe
480 // Der Returnwert ist die Anzahl Zeichen, die aus dem
481 // Format verwendet wurden.
482 
483 #ifdef _old_format_code_
484 // lasse diesen Code vorl"aufig drin, zum 'abgucken'
485 // der bisherigen Implementation
486 
487 static sal_uInt16 printfmtnum( double nNum, XubString& rRes, const XubString& rWFmt )
488 {
489     const String& rFmt = rWFmt;
490     char    cFill  = ' ';           // Fuellzeichen
491     char    cPre   = 0;             // Startzeichen( evtl. "$" )
492     short   nExpDig= 0;             // Anzahl Exponentstellen
493     short   nPrec  = 0;             // Anzahl Nachkommastellen
494     short   nWidth = 0;             // Zahlenweite gesamnt
495     short   nLen;                   // Laenge konvertierte Zahl
496     sal_Bool    bPoint = sal_False;         // sal_True: mit 1000er Kommas
497     sal_Bool    bTrail = sal_False;         // sal_True, wenn folgendes Minus
498     sal_Bool    bSign  = sal_False;         // sal_True: immer mit Vorzeichen
499     sal_Bool    bNeg   = sal_False;         // sal_True: Zahl ist negativ
500     char    cBuf [1024];            // Zahlenpuffer
501     char  * p;
502     const char* pFmt = rFmt;
503     rRes.Erase();
504     // $$ und ** abfangen. Einfach wird als Zeichen ausgegeben.
505     if( *pFmt == '$' )
506       if( *++pFmt != '$' ) rRes += '$';
507     if( *pFmt == '*' )
508       if( *++pFmt != '*' ) rRes += '*';
509 
510     switch( *pFmt++ )
511     {
512         case 0:
513             break;
514         case '+':
515             bSign = sal_True; nWidth++; break;
516         case '*':
517             nWidth++; cFill = '*';
518             if( *pFmt == '$' ) nWidth++, pFmt++, cPre = '$';
519             break;
520         case '$':
521             nWidth++; cPre = '$'; break;
522         case '#':
523         case '.':
524         case ',':
525             pFmt--; break;
526     }
527     // Vorkomma:
528     for( ;; )
529     {
530         while( *pFmt == '#' ) pFmt++, nWidth++;
531         // 1000er Kommas?
532         if( *pFmt == ',' )
533         {
534             nWidth++; pFmt++; bPoint = sal_True;
535         } else break;
536     }
537     // Nachkomma:
538     if( *pFmt == '.' )
539     {
540         while( *++pFmt == '#' ) nPrec++;
541         nWidth += nPrec + 1;
542     }
543     // Exponent:
544     while( *pFmt == '^' )
545         pFmt++, nExpDig++, nWidth++;
546     // Folgendes Minus:
547     if( !bSign && *pFmt == '-' )
548         pFmt++, bTrail = sal_True;
549 
550     // Zahl konvertieren:
551     if( nPrec > 15 ) nPrec = 15;
552     if( nNum < 0.0 ) nNum = -nNum, bNeg = sal_True;
553     p = cBuf;
554     if( bSign ) *p++ = bNeg ? '-' : '+';
555     myftoa( nNum, p, nPrec, nExpDig, bPoint, sal_False );
556     nLen = strlen( cBuf );
557 
558     // Überlauf?
559     if( cPre ) nLen++;
560     if( nLen > nWidth ) rRes += '%';
561     else {
562         nWidth -= nLen;
563         while( nWidth-- ) rRes += (xub_Unicode)cFill;
564         if( cPre ) rRes += (xub_Unicode)cPre;
565     }
566     rRes += (xub_Unicode*)&(cBuf[0]);
567     if( bTrail )
568         rRes += bNeg ? '-' : ' ';
569 
570     return (sal_uInt16) ( pFmt - (const char*) rFmt );
571 }
572 
573 #endif //_old_format_code_
574 
575 static sal_uInt16 printfmtstr( const XubString& rStr, XubString& rRes, const XubString& rFmt )
576 {
577     const xub_Unicode* pStr = rStr.GetBuffer();
578     const xub_Unicode* pFmtStart = rFmt.GetBuffer();
579     const xub_Unicode* pFmt = pFmtStart;
580     rRes.Erase();
581     switch( *pFmt )
582     {
583         case '!':
584                 rRes += *pStr++; pFmt++; break;
585         case '\\':
586             do
587             {
588                 rRes += *pStr ? *pStr++ : static_cast< xub_Unicode >(' ');
589                 pFmt++;
590             } while( *pFmt != '\\' );
591             rRes += *pStr ? *pStr++ : static_cast< xub_Unicode >(' ');
592             pFmt++; break;
593         case '&':
594             rRes = rStr;
595             pFmt++; break;
596         default:
597             rRes = rStr;
598             break;
599     }
600     return (sal_uInt16) ( pFmt - pFmtStart );
601 }
602 
603 /////////////////////////////////////////////////////////////////////////
604 
605 sal_Bool SbxValue::Scan( const XubString& rSrc, sal_uInt16* pLen )
606 {
607     SbxError eRes = SbxERR_OK;
608     if( !CanWrite() )
609         eRes = SbxERR_PROP_READONLY;
610     else
611     {
612         double n;
613         SbxDataType t;
614         eRes = ImpScan( rSrc, n, t, pLen );
615         if( eRes == SbxERR_OK )
616         {
617             if( !IsFixed() )
618                 SetType( t );
619             PutDouble( n );
620         }
621     }
622     if( eRes )
623     {
624         SetError( eRes ); return sal_False;
625     }
626     else
627         return sal_True;
628 }
629 
630 
631 ResMgr* implGetResMgr( void )
632 {
633     static ResMgr* pResMgr = NULL;
634     if( !pResMgr )
635     {
636         ::com::sun::star::lang::Locale aLocale = Application::GetSettings().GetUILocale();
637         pResMgr = ResMgr::CreateResMgr(CREATEVERSIONRESMGR_NAME(sb), aLocale );
638     }
639     return pResMgr;
640 }
641 
642 class SbxValueFormatResId : public ResId
643 {
644 public:
645     SbxValueFormatResId( sal_uInt16 nId )
646         : ResId( nId, *implGetResMgr() )
647     {}
648 };
649 
650 
651 enum VbaFormatType
652 {
653     VBA_FORMAT_TYPE_OFFSET, // standard number format
654     VBA_FORMAT_TYPE_USERDEFINED, // user defined number format
655     VBA_FORMAT_TYPE_NULL
656 };
657 
658 struct VbaFormatInfo
659 {
660     VbaFormatType meType;
661     const char* mpVbaFormat; // Format string in vba
662     NfIndexTableOffset meOffset; // SvNumberFormatter format index, if meType = VBA_FORMAT_TYPE_OFFSET
663     const char* mpOOoFormat; // if meType = VBA_FORMAT_TYPE_USERDEFINED
664 };
665 
666 #define VBA_FORMAT_OFFSET( pcUtf8, eOffset ) \
667     { VBA_FORMAT_TYPE_OFFSET, pcUtf8, eOffset, 0 }
668 
669 #define VBA_FORMAT_USERDEFINED( pcUtf8, pcDefinedUtf8 ) \
670     { VBA_FORMAT_TYPE_USERDEFINED, pcUtf8, NF_NUMBER_STANDARD, pcDefinedUtf8 }
671 
672 static VbaFormatInfo pFormatInfoTable[] =
673 {
674     VBA_FORMAT_OFFSET( "Long Date", NF_DATE_SYSTEM_LONG ),
675     VBA_FORMAT_USERDEFINED( "Medium Date", "DD-MMM-YY" ),
676     VBA_FORMAT_OFFSET( "Short Date", NF_DATE_SYSTEM_SHORT ),
677     VBA_FORMAT_USERDEFINED( "Long Time", "H:MM:SS AM/PM" ),
678     VBA_FORMAT_OFFSET( "Medium Time", NF_TIME_HHMMAMPM ),
679     VBA_FORMAT_OFFSET( "Short Time", NF_TIME_HHMM ),
680     VBA_FORMAT_OFFSET( "ddddd", NF_DATE_SYSTEM_SHORT ),
681     VBA_FORMAT_OFFSET( "dddddd", NF_DATE_SYSTEM_LONG ),
682     VBA_FORMAT_USERDEFINED( "ttttt", "H:MM:SS AM/PM" ),
683     VBA_FORMAT_OFFSET( "ww", NF_DATE_WW ),
684     { VBA_FORMAT_TYPE_NULL, 0, NF_INDEX_TABLE_ENTRIES, 0 }
685 };
686 
687 VbaFormatInfo* getFormatInfo( const String& rFmt )
688 {
689     VbaFormatInfo* pInfo = NULL;
690     sal_Int16 i = 0;
691     while( (pInfo = pFormatInfoTable + i )->mpVbaFormat != NULL )
692     {
693         if( rFmt.EqualsIgnoreCaseAscii( pInfo->mpVbaFormat ) )
694             break;
695         i++;
696     }
697     return pInfo;
698 }
699 
700 #define VBAFORMAT_GENERALDATE       "General Date"
701 #define VBAFORMAT_C                 "c"
702 #define VBAFORMAT_N                 "n"
703 #define VBAFORMAT_NN                "nn"
704 #define VBAFORMAT_W                 "w"
705 #define VBAFORMAT_Y                 "y"
706 #define VBAFORMAT_LOWERCASE         "<"
707 #define VBAFORMAT_UPPERCASE         ">"
708 
709 // From methods1.cxx
710 sal_Int16 implGetWeekDay( double aDate, bool bFirstDayParam = false, sal_Int16 nFirstDay = 0 );
711 // from methods.cxx
712 sal_Int16 implGetMinute( double dDate );
713 sal_Int16 implGetDateYear( double aDate );
714 sal_Bool implDateSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay, double& rdRet );
715 
716 void SbxValue::Format( XubString& rRes, const XubString* pFmt ) const
717 {
718     short nComma = 0;
719     double d = 0;
720 
721     // pflin, It is better to use SvNumberFormatter to handle the date/time/number format.
722     // the SvNumberFormatter output is mostly compatible with
723     // VBA output besides the AOO-BASIC output
724     if( pFmt && !SbxBasicFormater::isBasicFormat( *pFmt ) )
725     {
726         String aStr = GetString();
727 
728         SvtSysLocale aSysLocale;
729         const CharClass& rCharClass = aSysLocale.GetCharClass();
730 
731         if( pFmt->EqualsIgnoreCaseAscii( VBAFORMAT_LOWERCASE ) )
732         {
733             rCharClass.toLower( aStr );
734             rRes = aStr;
735             return;
736         }
737         if( pFmt->EqualsIgnoreCaseAscii( VBAFORMAT_UPPERCASE ) )
738         {
739             rCharClass.toUpper( aStr );
740             rRes = aStr;
741             return;
742         }
743 
744         LanguageType eLangType = GetpApp()->GetSettings().GetLanguage();
745         com::sun::star::uno::Reference< com::sun::star::lang::XMultiServiceFactory >
746             xFactory = comphelper::getProcessServiceFactory();
747         SvNumberFormatter aFormatter( xFactory, eLangType );
748 
749         sal_uInt32 nIndex;
750         xub_StrLen nCheckPos = 0;
751         short nType;
752         double nNumber;
753         Color* pCol;
754 
755         sal_Bool bSuccess = aFormatter.IsNumberFormat( aStr, nIndex, nNumber );
756 
757         // number format, use SvNumberFormatter to handle it.
758         if( bSuccess )
759         {
760             String aFmtStr = *pFmt;
761             VbaFormatInfo* pInfo = getFormatInfo( aFmtStr );
762             if( pInfo && pInfo->meType != VBA_FORMAT_TYPE_NULL )
763             {
764                 if( pInfo->meType == VBA_FORMAT_TYPE_OFFSET )
765                 {
766                     nIndex = aFormatter.GetFormatIndex( pInfo->meOffset, eLangType );
767                 }
768                 else
769                 {
770                     aFmtStr.AssignAscii( pInfo->mpOOoFormat );
771                     aFormatter.PutandConvertEntry( aFmtStr, nCheckPos, nType, nIndex, LANGUAGE_ENGLISH, eLangType );
772                 }
773                 aFormatter.GetOutputString( nNumber, nIndex, rRes, &pCol );
774             }
775             else if( aFmtStr.EqualsIgnoreCaseAscii( VBAFORMAT_GENERALDATE )
776                     || aFmtStr.EqualsIgnoreCaseAscii( VBAFORMAT_C ))
777             {
778                 if( nNumber <=-1.0 || nNumber >= 1.0 )
779                 {
780                     // short date
781                     nIndex = aFormatter.GetFormatIndex( NF_DATE_SYSTEM_SHORT, eLangType );
782                     aFormatter.GetOutputString( nNumber, nIndex, rRes, &pCol );
783 
784                     // long time
785                     if( floor( nNumber ) != nNumber )
786                     {
787                         aFmtStr.AssignAscii( "H:MM:SS AM/PM" );
788                         aFormatter.PutandConvertEntry( aFmtStr, nCheckPos, nType, nIndex, LANGUAGE_ENGLISH, eLangType );
789                         String aTime;
790                         aFormatter.GetOutputString( nNumber, nIndex, aTime, &pCol );
791                         rRes.AppendAscii(" ");
792                         rRes += aTime;
793                     }
794                 }
795                 else
796                 {
797                     // long time only
798                     aFmtStr.AssignAscii( "H:MM:SS AM/PM" );
799                     aFormatter.PutandConvertEntry( aFmtStr, nCheckPos, nType, nIndex, LANGUAGE_ENGLISH, eLangType );
800                     aFormatter.GetOutputString( nNumber, nIndex, rRes, &pCol );
801                 }
802             }
803             else if( aFmtStr.EqualsIgnoreCaseAscii( VBAFORMAT_N )
804                     || aFmtStr.EqualsIgnoreCaseAscii( VBAFORMAT_NN ))
805             {
806                 sal_Int32 nMin = implGetMinute( nNumber );
807                 if( nMin < 10 && aFmtStr.EqualsIgnoreCaseAscii( VBAFORMAT_NN ) )
808                 {
809                     // Minute in two digits
810                      sal_Unicode* p = rRes.AllocBuffer( 2 );
811                      *p++ = '0';
812                      *p = sal_Unicode( '0' + nMin );
813                 }
814                 else
815                 {
816                     rRes = String::CreateFromInt32( nMin );
817                 }
818             }
819             else if( aFmtStr.EqualsIgnoreCaseAscii( VBAFORMAT_W ))
820             {
821                 sal_Int32 nWeekDay = implGetWeekDay( nNumber );
822                 rRes = String::CreateFromInt32( nWeekDay );
823             }
824             else if( aFmtStr.EqualsIgnoreCaseAscii( VBAFORMAT_Y ))
825             {
826                 sal_Int16 nYear = implGetDateYear( nNumber );
827                 double dBaseDate;
828                 implDateSerial( nYear, 1, 1, dBaseDate );
829                 sal_Int32 nYear32 = 1 + sal_Int32( nNumber - dBaseDate );
830                 rRes = String::CreateFromInt32( nYear32 );
831             }
832             else
833             {
834                 aFormatter.PutandConvertEntry( aFmtStr, nCheckPos, nType, nIndex, LANGUAGE_ENGLISH, eLangType );
835                 aFormatter.GetOutputString( nNumber, nIndex, rRes, &pCol );
836             }
837 
838             return;
839         }
840     }
841 
842     SbxDataType eType = GetType();
843     switch( eType )
844     {
845         case SbxCHAR:
846         case SbxBYTE:
847         case SbxINTEGER:
848         case SbxUSHORT:
849         case SbxLONG:
850         case SbxULONG:
851         case SbxINT:
852         case SbxUINT:
853         case SbxNULL:       // #45929 NULL mit durchschummeln
854             nComma = 0;     goto cvt;
855         case SbxSINGLE:
856             nComma = 6;     goto cvt;
857         case SbxDOUBLE:
858             nComma = 14;
859 
860         cvt:
861             if( eType != SbxNULL )
862                 d = GetDouble();
863 
864             // #45355 weiterer Einsprungpunkt für isnumeric-String
865         cvt2:
866             if( pFmt )
867             {
868                 // hole die 'statischen' Daten f"ur Sbx
869                 SbxAppData* pData = GetSbxData_Impl();
870 
871                 LanguageType eLangType = GetpApp()->GetSettings().GetLanguage();
872                 if( pData->pBasicFormater )
873                 {
874                     if( pData->eBasicFormaterLangType != eLangType )
875                     {
876                         delete pData->pBasicFormater;
877                         pData->pBasicFormater = NULL;
878                     }
879                 }
880                 pData->eBasicFormaterLangType = eLangType;
881 
882                 // falls bisher noch kein BasicFormater-Objekt
883                 // existiert, so erzeuge dieses
884                 if( !pData->pBasicFormater )
885                 {
886                     SvtSysLocale aSysLocale;
887                     const LocaleDataWrapper& rData = aSysLocale.GetLocaleData();
888                     sal_Unicode cComma = rData.getNumDecimalSep().GetBuffer()[0];
889                     sal_Unicode c1000  = rData.getNumThousandSep().GetBuffer()[0];
890                     String aCurrencyStrg = rData.getCurrSymbol();
891 
892                     // Initialisierung des Basic-Formater-Hilfsobjekts:
893                     // hole die Ressourcen für die vordefinierten Ausgaben
894                     // des Format()-Befehls, z.B. für "On/Off".
895                     String aOnStrg = String( SbxValueFormatResId(
896                         STR_BASICKEY_FORMAT_ON ) );
897                     String aOffStrg = String( SbxValueFormatResId(
898                         STR_BASICKEY_FORMAT_OFF) );
899                     String aYesStrg = String( SbxValueFormatResId(
900                         STR_BASICKEY_FORMAT_YES) );
901                     String aNoStrg = String( SbxValueFormatResId(
902                         STR_BASICKEY_FORMAT_NO) );
903                     String aTrueStrg = String( SbxValueFormatResId(
904                         STR_BASICKEY_FORMAT_TRUE) );
905                     String aFalseStrg = String( SbxValueFormatResId(
906                         STR_BASICKEY_FORMAT_FALSE) );
907                     String aCurrencyFormatStrg = String( SbxValueFormatResId(
908                         STR_BASICKEY_FORMAT_CURRENCY) );
909                     // erzeuge das Basic-Formater-Objekt
910                     pData->pBasicFormater
911                         = new SbxBasicFormater( cComma,c1000,aOnStrg,aOffStrg,
912                                     aYesStrg,aNoStrg,aTrueStrg,aFalseStrg,
913                                     aCurrencyStrg,aCurrencyFormatStrg );
914                 }
915                 // Bem.: Aus Performance-Gründen wird nur EIN BasicFormater-
916                 //    Objekt erzeugt und 'gespeichert', dadurch erspart man
917                 //    sich das teure Ressourcen-Laden (für landesspezifische
918                 //    vordefinierte Ausgaben, z.B. "On/Off") und die ständige
919                 //    String-Erzeugungs Operationen.
920                 // ABER: dadurch ist dieser Code NICHT multithreading f"ahig !
921 
922                 // hier gibt es Probleme mit ;;;Null, da diese Methode nur aufgerufen
923                 // wird, wenn der SbxValue eine Zahl ist !!!
924                 // dazu koennte: pData->pBasicFormater->BasicFormatNull( *pFmt ); aufgerufen werden !
925                 if( eType != SbxNULL )
926                 {
927                     rRes = pData->pBasicFormater->BasicFormat( d ,*pFmt );
928                 }
929                 else
930                 {
931                     rRes = pData->pBasicFormater->BasicFormatNull( *pFmt );
932                 }
933 
934                 // Die alte Implementierung:
935                 //old: printfmtnum( GetDouble(), rRes, *pFmt );
936             }
937             else
938             {
939                 ::rtl::OUString aTmpString( rRes );
940                 ImpCvtNum( GetDouble(), nComma, aTmpString );
941                 rRes = aTmpString;
942             }
943             break;
944         case SbxSTRING:
945             if( pFmt )
946             {
947                 // #45355 wenn es numerisch ist, muss gewandelt werden
948                 if( IsNumericRTL() )
949                 {
950                     ScanNumIntnl( GetString(), d, /*bSingle*/sal_False );
951                     goto cvt2;
952                 }
953                 else
954                 {
955                     // Sonst String-Formatierung
956                     printfmtstr( GetString(), rRes, *pFmt );
957                 }
958             }
959             else
960                 rRes = GetString();
961             break;
962         default:
963             rRes = GetString();
964     }
965 }
966 
967 /* vim: set noet sw=4 ts=4: */
968