1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 .
21 #include <tools/errcode.hxx>
22 #include <vcl/svapp.hxx> // for SvtSysLocale
24 #include <basic/sbx.hxx>
25 #include <basic/sbxvar.hxx>
26 #include "sbxconv.hxx"
29 static OUString
ImpCurrencyToString( const sal_Int64
&rVal
)
31 bool isNeg
= ( rVal
< 0 );
32 sal_Int64 absVal
= isNeg
? -rVal
: rVal
;
34 sal_Unicode cDecimalSep
= '.';
36 sal_Unicode cThousandSep
= ',';
37 ImpGetIntntlSep( cDecimalSep
, cThousandSep
);
40 OUString aAbsStr
= OUString::valueOf( absVal
);
43 sal_Int32 initialLen
= aAbsStr
.getLength();
45 bool bLessThanOne
= false;
46 if ( initialLen
<= 4 ) // if less the 1
49 sal_Int32 nCapacity
= 6; // minimum e.g. 0.0000
53 nCapacity
= initialLen
+ 1;
57 sal_Int32 nThouSeparators
= ( initialLen
- 5 ) / 3;
58 nCapacity
+= nThouSeparators
;
66 aBuf
.setLength( nCapacity
);
69 sal_Int32 nDigitCount
= 0;
70 sal_Int32 nInsertIndex
= nCapacity
- 1;
71 sal_Int32 nEndIndex
= isNeg
? 1 : 0;
73 for ( sal_Int32 charCpyIndex
= aAbsStr
.getLength() - 1; nInsertIndex
>= nEndIndex
; ++nDigitCount
)
75 if ( nDigitCount
== 4 )
76 aBuf
[nInsertIndex
--] = cDecimalSep
;
78 if ( nDigitCount
> 4 && ! ( ( nDigitCount
- 4 ) % 3) )
79 aBuf
[nInsertIndex
--] = cThousandSep
;
81 if ( nDigitCount
< initialLen
)
82 aBuf
[nInsertIndex
--] = aAbsStr
[ charCpyIndex
-- ];
84 // Handle leading 0's to right of decimal point
85 // Note: in VBA the stringification is a little more complex
86 // but more natural as only the necessary digits
87 // to the right of the decimal places are displayed
88 // It would be great to conditionally be able to display like that too
90 // Val OOo (Cur) VBA (Cur)
91 // --- --------- ---------
95 aBuf
[nInsertIndex
--] = (sal_Unicode
)'0';
98 aBuf
[nInsertIndex
] = (sal_Unicode
)'-';
100 aAbsStr
= aBuf
.makeStringAndClear();
105 static sal_Int64
ImpStringToCurrency( const OUString
&rStr
)
108 sal_Int32 nFractDigit
= 4;
110 sal_Unicode cDeciPnt
= sal_Unicode('.');
111 sal_Unicode c1000Sep
= sal_Unicode(',');
114 sal_Unicode cLocaleDeciPnt
, cLocale1000Sep
;
115 ImpGetIntntlSep( cLocaleDeciPnt
, cLocale1000Sep
);
117 // score each set of separators (Locale and Basic) on total number of matches
118 // if one set has more matches use that set
119 // if tied use the set with the only or rightmost decimal separator match
120 // currency is fixed pt system: usually expect the decimal pt, 1000sep may occur
121 sal_Int32 LocaleScore
= 0;
122 sal_Int32 LocaleLastDeci
= -1;
123 sal_Int32 LOBasicScore
= 0;
124 sal_Int32 LOBasicLastDeci
= -1;
126 for( int idx
=0; idx
<rStr
.getLength(); idx
++ )
128 if ( *(p
+idx
) == cLocaleDeciPnt
)
131 LocaleLastDeci
= idx
;
133 if ( *(p
+idx
) == cLocale1000Sep
)
136 if ( *(p
+idx
) == cDeciPnt
)
139 LOBasicLastDeci
= idx
;
141 if ( *(p
+idx
) == c1000Sep
)
144 if ( ( LocaleScore
> LOBasicScore
)
145 ||( LocaleScore
= LOBasicScore
&& LocaleLastDeci
> LOBasicLastDeci
) )
147 cDeciPnt
= cLocaleDeciPnt
;
148 c1000Sep
= cLocale1000Sep
;
152 // lets use the existing string number conversions
153 // there is a performance impact here ( multiple string copies )
154 // but better I think than a home brewed string parser, if we need a parser
155 // we should share some existing ( possibly from calc is there a currency
156 // conversion there ? #TODO check )
158 OUString
sTmp( rStr
.trim() );
159 const sal_Unicode
* p
= sTmp
.getStr();
161 // normalise string number by removeing thousands & decimal point separators
162 OUStringBuffer
sNormalisedNumString( sTmp
.getLength() + nFractDigit
);
164 if ( *p
== '-' || *p
== '+' )
165 sNormalisedNumString
.append( *p
);
167 while ( ( *p
>= '0' && *p
<= '9' ) )
169 sNormalisedNumString
.append( *p
++ );
170 // #TODO in vba mode set runtime error when a space ( or other )
171 // illegal character is found
176 bool bRoundUp
= false;
181 while( nFractDigit
&& *p
>= '0' && *p
<= '9' )
183 sNormalisedNumString
.append( *p
++ );
186 // Consume trailing content
189 // Round up if necessary
190 if( *p
>= '5' && *p
<= '9' )
192 while( *p
>= '0' && *p
<= '9' )
197 // can we raise error here ? ( previous behaviour was more forgiving )
198 // so... not sure that could bread existing code, lets see if anyone
201 if ( p
!= sTmp
.getStr() + sTmp
.getLength() )
202 SbxBase::SetError( SbxERR_CONVERSION
);
205 sNormalisedNumString
.append( sal_Unicode('0') );
209 sal_Int64 result
= sNormalisedNumString
.makeStringAndClear().toInt64();
217 sal_Int64
ImpGetCurrency( const SbxValues
* p
)
226 SbxBase::SetError( SbxERR_CONVERSION
);
231 nRes
= p
->nInt64
; break;
233 nRes
= (sal_Int64
)CURRENCY_FACTOR
* (sal_Int64
)(p
->nByte
);
236 nRes
= (sal_Int64
)CURRENCY_FACTOR
* (sal_Int64
)(p
->pChar
);
240 nRes
= (sal_Int64
)CURRENCY_FACTOR
* (sal_Int64
)(p
->nInteger
);
243 nRes
= (sal_Int64
)CURRENCY_FACTOR
* (sal_Int64
)(p
->nUShort
);
246 nRes
= (sal_Int64
)CURRENCY_FACTOR
* (sal_Int64
)(p
->nLong
);
249 nRes
= (sal_Int64
)CURRENCY_FACTOR
* (sal_Int64
)(p
->nULong
);
254 nRes
= p
->nInt64
* CURRENCY_FACTOR
; break;
256 // Huh, is the 'break' above intentional? That means this
257 // is unreachable, obviously. Avoid warning by ifdeffing
258 // this out for now. Do not delete this #if 0 block unless
259 // you know for sure the 'break' above is intentional.
260 if ( nRes
> SbxMAXSALINT64
)
262 SbxBase::SetError( SbxERR_OVERFLOW
); nRes
= SbxMAXSALINT64
;
267 nRes
= p
->nInt64
* CURRENCY_FACTOR
; break;
270 if ( nRes
> SbxMAXSALINT64
)
272 SbxBase::SetError( SbxERR_OVERFLOW
); nRes
= SbxMAXSALINT64
;
274 else if ( nRes
< SbxMINSALINT64
)
276 SbxBase::SetError( SbxERR_OVERFLOW
); nRes
= SbxMINSALINT64
;
280 //TODO: bring back SbxINT64 types here for limits -1 with flag value at SAL_MAX/MIN
282 if( p
->nSingle
* CURRENCY_FACTOR
+ 0.5 > (float)SAL_MAX_INT64
283 || p
->nSingle
* CURRENCY_FACTOR
- 0.5 < (float)SAL_MIN_INT64
)
285 nRes
= SAL_MAX_INT64
;
286 if( p
->nSingle
* CURRENCY_FACTOR
- 0.5 < (float)SAL_MIN_INT64
)
287 nRes
= SAL_MIN_INT64
;
288 SbxBase::SetError( SbxERR_OVERFLOW
);
291 nRes
= ImpDoubleToCurrency( (double)p
->nSingle
);
296 if( p
->nDouble
* CURRENCY_FACTOR
+ 0.5 > (double)SAL_MAX_INT64
297 || p
->nDouble
* CURRENCY_FACTOR
- 0.5 < (double)SAL_MIN_INT64
)
299 nRes
= SAL_MAX_INT64
;
300 if( p
->nDouble
* CURRENCY_FACTOR
- 0.5 < (double)SAL_MIN_INT64
)
301 nRes
= SAL_MIN_INT64
;
302 SbxBase::SetError( SbxERR_OVERFLOW
);
305 nRes
= ImpDoubleToCurrency( p
->nDouble
);
309 case SbxBYREF
| SbxDECIMAL
:
313 p
->pDecimal
->getDouble( d
);
314 nRes
= ImpDoubleToCurrency( d
);
319 case SbxBYREF
| SbxSTRING
:
325 nRes
= ImpStringToCurrency( *p
->pOUString
);
329 SbxValue
* pVal
= PTR_CAST(SbxValue
,p
->pObj
);
331 nRes
= pVal
->GetCurrency();
334 SbxBase::SetError( SbxERR_NO_OBJECT
);
340 case SbxBYREF
| SbxCHAR
:
341 nRes
= (sal_Int64
)CURRENCY_FACTOR
* (sal_Int64
)(*p
->pChar
);
343 case SbxBYREF
| SbxBYTE
:
344 nRes
= (sal_Int64
)CURRENCY_FACTOR
* (sal_Int64
)(*p
->pByte
);
346 case SbxBYREF
| SbxBOOL
:
347 case SbxBYREF
| SbxINTEGER
:
348 nRes
= (sal_Int64
)CURRENCY_FACTOR
* (sal_Int64
)(*p
->pInteger
);
350 case SbxBYREF
| SbxERROR
:
351 case SbxBYREF
| SbxUSHORT
:
352 nRes
= (sal_Int64
)CURRENCY_FACTOR
* (sal_Int64
)(*p
->pUShort
);
355 // from here on had to be tested
356 case SbxBYREF
| SbxLONG
:
357 aTmp
.nLong
= *p
->pLong
; goto ref
;
358 case SbxBYREF
| SbxULONG
:
359 aTmp
.nULong
= *p
->pULong
; goto ref
;
360 case SbxBYREF
| SbxSINGLE
:
361 aTmp
.nSingle
= *p
->pSingle
; goto ref
;
362 case SbxBYREF
| SbxDATE
:
363 case SbxBYREF
| SbxDOUBLE
:
364 aTmp
.nDouble
= *p
->pDouble
; goto ref
;
365 case SbxBYREF
| SbxCURRENCY
:
366 case SbxBYREF
| SbxSALINT64
:
367 aTmp
.nInt64
= *p
->pnInt64
; goto ref
;
368 case SbxBYREF
| SbxSALUINT64
:
369 aTmp
.uInt64
= *p
->puInt64
; goto ref
;
371 aTmp
.eType
= SbxDataType( p
->eType
& ~SbxBYREF
);
372 p
= &aTmp
; goto start
;
375 SbxBase::SetError( SbxERR_CONVERSION
);
382 void ImpPutCurrency( SbxValues
* p
, const sal_Int64 r
)
388 // Here are tests necessary
390 aTmp
.pChar
= &p
->nChar
; goto direct
;
392 aTmp
.pByte
= &p
->nByte
; goto direct
;
395 aTmp
.pInteger
= &p
->nInteger
; goto direct
;
397 aTmp
.pLong
= &p
->nLong
; goto direct
;
399 aTmp
.pULong
= &p
->nULong
; goto direct
;
402 aTmp
.pUShort
= &p
->nUShort
; goto direct
;
404 aTmp
.eType
= SbxDataType( p
->eType
| SbxBYREF
);
405 p
= &aTmp
; goto start
;
407 // from here no longer
409 p
->nSingle
= (float)( r
/ CURRENCY_FACTOR
); break;
412 p
->nDouble
= ImpCurrencyToDouble( r
); break;
414 p
->uInt64
= r
/ CURRENCY_FACTOR
; break;
416 p
->nInt64
= r
/ CURRENCY_FACTOR
; break;
419 p
->nInt64
= r
; break;
422 case SbxBYREF
| SbxDECIMAL
:
424 SbxDecimal
* pDec
= ImpCreateDecimal( p
);
425 if( !pDec
->setDouble( ImpCurrencyToDouble( r
) / CURRENCY_FACTOR
) )
426 SbxBase::SetError( SbxERR_OVERFLOW
);
429 case SbxBYREF
| SbxSTRING
:
433 p
->pOUString
= new OUString
;
435 *p
->pOUString
= ImpCurrencyToString( r
);
439 SbxValue
* pVal
= PTR_CAST(SbxValue
,p
->pObj
);
441 pVal
->PutCurrency( r
);
443 SbxBase::SetError( SbxERR_NO_OBJECT
);
446 case SbxBYREF
| SbxCHAR
:
448 sal_Int64 val
= r
/ CURRENCY_FACTOR
;
449 if( val
> SbxMAXCHAR
)
451 SbxBase::SetError( SbxERR_OVERFLOW
); val
= SbxMAXCHAR
;
453 else if( val
< SbxMINCHAR
)
455 SbxBase::SetError( SbxERR_OVERFLOW
); val
= SbxMINCHAR
;
457 *p
->pChar
= (sal_Unicode
) val
; break;
459 case SbxBYREF
| SbxBYTE
:
461 sal_Int64 val
= r
/ CURRENCY_FACTOR
;
462 if( val
> SbxMAXBYTE
)
464 SbxBase::SetError( SbxERR_OVERFLOW
); val
= SbxMAXBYTE
;
468 SbxBase::SetError( SbxERR_OVERFLOW
); val
= 0;
470 *p
->pByte
= (sal_uInt8
) val
; break;
472 case SbxBYREF
| SbxINTEGER
:
473 case SbxBYREF
| SbxBOOL
:
475 sal_Int64 val
= r
/ CURRENCY_FACTOR
;
478 SbxBase::SetError( SbxERR_OVERFLOW
); val
= SbxMAXINT
;
480 else if( r
< SbxMININT
)
482 SbxBase::SetError( SbxERR_OVERFLOW
); val
= SbxMININT
;
484 *p
->pInteger
= (sal_uInt16
) val
; break;
486 case SbxBYREF
| SbxERROR
:
487 case SbxBYREF
| SbxUSHORT
:
489 sal_Int64 val
= r
/ CURRENCY_FACTOR
;
490 if( val
> SbxMAXUINT
)
492 SbxBase::SetError( SbxERR_OVERFLOW
); val
= SbxMAXUINT
;
496 SbxBase::SetError( SbxERR_OVERFLOW
); val
= 0;
498 *p
->pUShort
= (sal_uInt16
) val
; break;
500 case SbxBYREF
| SbxLONG
:
502 sal_Int64 val
= r
/ CURRENCY_FACTOR
;
503 if( val
> SbxMAXLNG
)
505 SbxBase::SetError( SbxERR_OVERFLOW
); val
= SbxMAXLNG
;
507 else if( val
< SbxMINLNG
)
509 SbxBase::SetError( SbxERR_OVERFLOW
); val
= SbxMINLNG
;
511 *p
->pLong
= (sal_Int32
) val
; break;
513 case SbxBYREF
| SbxULONG
:
515 sal_Int64 val
= r
/ CURRENCY_FACTOR
;
516 if( val
> SbxMAXULNG
)
518 SbxBase::SetError( SbxERR_OVERFLOW
); val
= SbxMAXULNG
;
522 SbxBase::SetError( SbxERR_OVERFLOW
); val
= 0;
524 *p
->pULong
= (sal_uInt32
) val
; break;
526 case SbxBYREF
| SbxCURRENCY
:
527 *p
->pnInt64
= r
; break;
528 case SbxBYREF
| SbxSALINT64
:
529 *p
->pnInt64
= r
/ CURRENCY_FACTOR
; break;
530 case SbxBYREF
| SbxSALUINT64
:
531 *p
->puInt64
= (sal_uInt64
)r
/ CURRENCY_FACTOR
; break;
532 case SbxBYREF
| SbxSINGLE
:
533 p
->nSingle
= (float)( r
/ CURRENCY_FACTOR
); break;
534 case SbxBYREF
| SbxDATE
:
535 case SbxBYREF
| SbxDOUBLE
:
536 *p
->pDouble
= ImpCurrencyToDouble( r
); break;
538 SbxBase::SetError( SbxERR_CONVERSION
);
542 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */