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 .
20 #include <sal/config.h>
23 #include <string_view>
25 #include <config_features.h>
27 #include <comphelper/errcode.hxx>
28 #include <unotools/resmgr.hxx>
29 #include "sbxconv.hxx"
31 #include <unotools/syslocale.hxx>
32 #include <unotools/charclass.hxx>
34 #include <vcl/svapp.hxx>
35 #include <vcl/settings.hxx>
39 #include <sbxbase.hxx>
40 #include <sbintern.hxx>
41 #include <sbxform.hxx>
44 #include <runtime.hxx>
45 #include <strings.hrc>
47 #include <rtl/character.hxx>
48 #include <rtl/math.hxx>
49 #include <svl/numformat.hxx>
50 #include <svl/zforlist.hxx>
51 #include <o3tl/temporary.hxx>
52 #include <o3tl/string_view.hxx>
53 #include <officecfg/Office/Scripting.hxx>
56 void ImpGetIntntlSep( sal_Unicode
& rcDecimalSep
, sal_Unicode
& rcThousandSep
, sal_Unicode
& rcDecimalSepAlt
)
58 SvtSysLocale aSysLocale
;
59 const LocaleDataWrapper
& rData
= aSysLocale
.GetLocaleData();
60 rcDecimalSep
= rData
.getNumDecimalSep()[0];
61 rcThousandSep
= rData
.getNumThousandSep()[0];
62 rcDecimalSepAlt
= rData
.getNumDecimalSepAlt().toChar();
66 static bool ImpStrChr( std::u16string_view str
, sal_Unicode c
) { return str
.find(c
) != std::u16string_view::npos
; }
69 // scanning a string according to BASIC-conventions
70 // but exponent may also be a D, so data type is SbxDOUBLE
71 // conversion error if data type is fixed and it doesn't fit
73 ErrCode
ImpScan( std::u16string_view rWSrc
, double& nVal
, SbxDataType
& rType
,
74 sal_Int32
* pLen
, bool* pHasNumber
, bool bOnlyIntntl
)
76 sal_Unicode cDecSep
, cGrpSep
, cDecSepAlt
;
79 ImpGetIntntlSep(cDecSep
, cGrpSep
, cDecSepAlt
);
80 // Ensure that the decimal separator alternative is really one.
81 if (cDecSepAlt
== cDecSep
)
87 cGrpSep
= 0; // no group separator accepted in non-i18n
91 auto const pStart
= rWSrc
.begin();
95 SbxDataType eScanType
= SbxSINGLE
;
96 while (p
!= rWSrc
.end() && (*p
== ' ' || *p
== '\t'))
98 if (p
!= rWSrc
.end() && *p
== '+')
100 else if (p
!= rWSrc
.end() && *p
== '-')
105 #if HAVE_FEATURE_SCRIPTING
106 if (SbiRuntime::isVBAEnabled())
108 while (p
!= rWSrc
.end() && (*p
== ' ' || *p
== '\t'))
112 const auto pNumberStart
= p
;
114 && (rtl::isAsciiDigit(*p
)
115 || ((*p
== cDecSep
|| (cGrpSep
&& *p
== cGrpSep
) || (cDecSepAlt
&& *p
== cDecSepAlt
))
116 && p
+ 1 != rWSrc
.end() && rtl::isAsciiDigit(*(p
+ 1)))))
121 short ncdig
= 0; // number of digits after decimal point
122 OUStringBuffer
aSearchStr("0123456789DEde" + OUStringChar(cDecSep
));
124 aSearchStr
.append(cDecSepAlt
);
126 aSearchStr
.append(cGrpSep
);
127 OUStringBuffer
aBuf(rWSrc
.end() - p
);
128 for (; p
!= rWSrc
.end() && ImpStrChr(aSearchStr
, *p
); ++p
)
130 if (rtl::isAsciiDigit(*p
))
140 else if (cGrpSep
&& *p
== cGrpSep
)
144 else if (*p
== cDecSep
|| (cDecSepAlt
&& *p
== cDecSepAlt
))
147 return ERRCODE_BASIC_CONVERSION
;
150 // Use the separator that is passed to stringToDouble()
151 aBuf
.append(cDecSep
);
156 return ERRCODE_BASIC_CONVERSION
;
159 if( *p
== 'D' || *p
== 'd' )
160 eScanType
= SbxDOUBLE
;
162 if (auto pNext
= p
+ 1; pNext
!= rWSrc
.end())
166 else if (*pNext
== '-')
175 rtl_math_ConversionStatus eStatus
= rtl_math_ConversionStatus_Ok
;
176 sal_Int32 nParseEnd
= 0;
177 nVal
= rtl::math::stringToDouble(aBuf
, cDecSep
, cGrpSep
, &eStatus
, &nParseEnd
);
178 if( eStatus
!= rtl_math_ConversionStatus_Ok
|| nParseEnd
!= aBuf
.getLength() )
179 return ERRCODE_BASIC_CONVERSION
;
181 if( !decsep
&& !exp
)
183 if( nVal
>= SbxMININT
&& nVal
<= SbxMAXINT
)
184 eScanType
= SbxINTEGER
;
185 else if( nVal
>= SbxMINLNG
&& nVal
<= SbxMAXLNG
)
189 // too many numbers for SINGLE?
190 if( ndig
> 15 || ncdig
> 6 )
191 eScanType
= SbxDOUBLE
;
194 static constexpr std::u16string_view pTypes
= u
"%!&#";
195 if (p
!= rWSrc
.end() && ImpStrChr(pTypes
, *p
))
198 // hex/octal number? read in and convert:
199 else if (p
!= rWSrc
.end() && *p
== '&')
201 if (++p
== rWSrc
.end())
202 return ERRCODE_BASIC_CONVERSION
;
204 auto isValidDigit
= rtl::isAsciiHexDigit
<sal_Unicode
>;
211 isValidDigit
= rtl::isAsciiOctalDigit
<sal_Unicode
>;
219 return ERRCODE_BASIC_CONVERSION
;
221 const auto pDigitsStart
= p
;
222 for (; p
!= rWSrc
.end() && rtl::isAsciiAlphanumeric(*p
); ++p
)
224 if (!isValidDigit(*p
))
225 return ERRCODE_BASIC_CONVERSION
;
227 if (p
- pDigitsStart
> ndig
)
228 return ERRCODE_BASIC_CONVERSION
;
229 sal_Int32 l
= o3tl::toInt32(rWSrc
.substr(pDigitsStart
- pStart
, p
- pDigitsStart
), base
);
230 if (p
!= rWSrc
.end() && *p
== '&')
232 nVal
= static_cast<double>(l
);
233 if( l
>= SbxMININT
&& l
<= SbxMAXINT
)
234 eScanType
= SbxINTEGER
;
236 #if HAVE_FEATURE_SCRIPTING
237 else if ( SbiRuntime::isVBAEnabled() )
239 return ERRCODE_BASIC_CONVERSION
;
242 const auto pNumberEnd
= p
;
243 // tdf#146672 - skip whitespaces and tabs at the end of the scanned string
244 while (p
!= rWSrc
.end() && (*p
== ' ' || *p
== '\t'))
249 *pHasNumber
= pNumberEnd
> pNumberStart
;
256 ErrCode
ImpScan(std::u16string_view rSrc
, double& nVal
, SbxDataType
& rType
, sal_Int32
* pLen
)
258 using namespace officecfg::Office::Scripting
;
259 static const bool bEnv
= std::getenv("LIBREOFFICE6FLOATINGPOINTMODE") != nullptr;
260 bool bMode
= bEnv
|| Basic::Compatibility::UseLibreOffice6FloatingPointConversion::get();
262 return ImpScan(rSrc
, nVal
, rType
, pLen
, nullptr, !bMode
);
265 // port for CDbl in the Basic
266 ErrCode
SbxValue::ScanNumIntnl( const OUString
& rSrc
, double& nVal
, bool bSingle
)
269 ErrCode nRetError
= ImpScan( rSrc
, nVal
, o3tl::temporary(SbxDataType()), &nLen
, nullptr,
270 /*bOnlyIntntl*/true );
272 if( nRetError
== ERRCODE_NONE
&& nLen
!= rSrc
.getLength() )
274 nRetError
= ERRCODE_BASIC_CONVERSION
;
278 SbxValues
aValues( nVal
);
279 nVal
= static_cast<double>(ImpGetSingle( &aValues
)); // here error at overflow
284 // The number is prepared unformattedly with the given number of
285 // NK-positions. A leading minus is added if applicable.
286 // This routine is public because it's also used by the Put-functions
287 // in the class SbxImpSTRING.
289 void ImpCvtNum( double nNum
, short nPrec
, OUString
& rRes
, bool bCoreString
)
291 sal_Unicode cDecimalSep
;
295 ImpGetIntntlSep(cDecimalSep
, o3tl::temporary(sal_Unicode()), o3tl::temporary(sal_Unicode()));
297 // tdf#143575 - use rtl::math::doubleToUString to convert numbers to strings in basic
298 rRes
= rtl::math::doubleToUString(nNum
, rtl_math_StringFormat_Automatic
, nPrec
, cDecimalSep
, true);
301 // formatted number output
303 static void printfmtstr(std::u16string_view rStr
, OUString
& rRes
, std::u16string_view rFmt
)
308 OUStringBuffer aTemp
;
309 auto pStr
= rStr
.begin();
310 auto pFmt
= rFmt
.begin();
315 if (pStr
!= rStr
.end())
321 aTemp
.append(pStr
!= rStr
.end() ? *pStr
++ : u
' ');
322 } while (++pFmt
!= rFmt
.end() && *pFmt
!= '\\');
323 aTemp
.append(pStr
!= rStr
.end() ? *pStr
: u
' ');
330 rRes
= aTemp
.makeStringAndClear();
334 bool SbxValue::Scan(std::u16string_view rSrc
, sal_Int32
* pLen
)
336 ErrCode eRes
= ERRCODE_NONE
;
339 eRes
= ERRCODE_BASIC_PROP_READONLY
;
345 eRes
= ImpScan( rSrc
, n
, t
, pLen
);
346 if( eRes
== ERRCODE_NONE
)
366 std::locale
BasResLocale()
368 return Translate::Create("sb");
371 OUString
BasResId(TranslateId aId
)
373 return Translate::get(aId
, BasResLocale());
379 enum class VbaFormatType
381 Offset
, // standard number format
382 UserDefined
, // user defined number format
385 #if HAVE_FEATURE_SCRIPTING
389 VbaFormatType meType
;
390 std::u16string_view mpVbaFormat
; // Format string in vba
391 NfIndexTableOffset meOffset
; // SvNumberFormatter format index, if meType = VbaFormatType::Offset
392 OUString mpOOoFormat
; // if meType = VbaFormatType::UserDefined
395 const VbaFormatInfo
* getFormatInfo( std::u16string_view rFmt
)
397 static constexpr const VbaFormatInfo formatInfoTable
[] =
399 { VbaFormatType::Offset
, u
"Long Date", NF_DATE_SYSTEM_LONG
, u
""_ustr
},
400 { VbaFormatType::UserDefined
, u
"Medium Date", NF_NUMBER_STANDARD
, u
"DD-MMM-YY"_ustr
},
401 { VbaFormatType::Offset
, u
"Short Date", NF_DATE_SYSTEM_SHORT
, u
""_ustr
},
402 { VbaFormatType::UserDefined
, u
"Long Time", NF_NUMBER_STANDARD
, u
"H:MM:SS AM/PM"_ustr
},
403 { VbaFormatType::Offset
, u
"Medium Time", NF_TIME_HHMMAMPM
, u
""_ustr
},
404 { VbaFormatType::Offset
, u
"Short Time", NF_TIME_HHMM
, u
""_ustr
},
405 { VbaFormatType::Offset
, u
"ddddd", NF_DATE_SYSTEM_SHORT
, u
""_ustr
},
406 { VbaFormatType::Offset
, u
"dddddd", NF_DATE_SYSTEM_LONG
, u
""_ustr
},
407 { VbaFormatType::UserDefined
, u
"ttttt", NF_NUMBER_STANDARD
, u
"H:MM:SS AM/PM"_ustr
},
408 { VbaFormatType::Offset
, u
"ww", NF_DATE_WW
, u
""_ustr
},
411 for (auto& info
: formatInfoTable
)
412 if (o3tl::equalsIgnoreAsciiCase(rFmt
, info
.mpVbaFormat
))
418 void BasicFormatNum(double d
, const OUString
& rFmt
, OUString
& rRes
)
420 SbxAppData
& rAppData
= GetSbxData_Impl();
422 LanguageType eLangType
= Application::GetSettings().GetLanguageTag().getLanguageType();
423 if (rAppData
.pBasicFormater
)
424 if (rAppData
.eBasicFormaterLangType
!= eLangType
)
425 rAppData
.pBasicFormater
.reset();
426 rAppData
.eBasicFormaterLangType
= eLangType
;
428 if (!rAppData
.pBasicFormater
)
430 SvtSysLocale aSysLocale
;
431 const LocaleDataWrapper
& rData
= aSysLocale
.GetLocaleData();
432 sal_Unicode cComma
= rData
.getNumDecimalSep()[0];
433 sal_Unicode c1000
= rData
.getNumThousandSep()[0];
434 const OUString
& aCurrencyStrg
= rData
.getCurrSymbol();
436 // initialize the Basic-formater help object:
437 // get resources for predefined output
438 // of the Format()-command, e. g. for "On/Off"
439 OUString aOnStrg
= BasResId(STR_BASICKEY_FORMAT_ON
);
440 OUString aOffStrg
= BasResId(STR_BASICKEY_FORMAT_OFF
);
441 OUString aYesStrg
= BasResId(STR_BASICKEY_FORMAT_YES
);
442 OUString aNoStrg
= BasResId(STR_BASICKEY_FORMAT_NO
);
443 OUString aTrueStrg
= BasResId(STR_BASICKEY_FORMAT_TRUE
);
444 OUString aFalseStrg
= BasResId(STR_BASICKEY_FORMAT_FALSE
);
445 OUString aCurrencyFormatStrg
= BasResId(STR_BASICKEY_FORMAT_CURRENCY
);
447 rAppData
.pBasicFormater
= std::make_unique
<SbxBasicFormater
>(
448 cComma
, c1000
, aOnStrg
, aOffStrg
, aYesStrg
, aNoStrg
, aTrueStrg
, aFalseStrg
,
449 aCurrencyStrg
, aCurrencyFormatStrg
);
451 // Remark: For performance reasons there's only ONE BasicFormater-
452 // object created and 'stored', so that the expensive resource-
453 // loading is saved (for country-specific predefined outputs,
454 // e. g. "On/Off") and the continuous string-creation
456 // BUT: therefore this code is NOT multithreading capable!
457 rRes
= rAppData
.pBasicFormater
->BasicFormat(d
, rFmt
);
460 void BasicFormatNum(double d
, const OUString
* pFmt
, SbxDataType eType
, OUString
& rRes
)
463 BasicFormatNum(d
, *pFmt
, rRes
);
465 ImpCvtNum(d
, eType
== SbxSINGLE
? 6 : eType
== SbxDOUBLE
? 14 : 0, rRes
);
468 #if HAVE_FEATURE_SCRIPTING
469 // For numeric types, takes the number directly; otherwise, tries to take string and convert it
470 bool GetNumberIntl(const SbxValue
& val
, double& ret
)
472 switch (val
.GetType())
485 ret
= val
.GetDouble();
489 return SbxValue::ScanNumIntnl(val
.GetOUString(), ret
) == ERRCODE_NONE
;
495 void SbxValue::Format( OUString
& rRes
, const OUString
* pFmt
) const
499 if (*pFmt
== "<") // VBA lowercase
501 rRes
= SvtSysLocale().GetCharClass().lowercase(GetOUString());
504 if (*pFmt
== ">") // VBA uppercase
506 rRes
= SvtSysLocale().GetCharClass().uppercase(GetOUString());
510 // pflin, It is better to use SvNumberFormatter to handle the date/time/number format.
511 // the SvNumberFormatter output is mostly compatible with
512 // VBA output besides the OOo-basic output
513 #if HAVE_FEATURE_SCRIPTING
514 // number format, use SvNumberFormatter to handle it.
515 if (double nNumber
; !SbxBasicFormater::isBasicFormat(*pFmt
) && GetNumberIntl(*this, nNumber
))
517 LanguageType eLangType
= Application::GetSettings().GetLanguageTag().getLanguageType();
518 std::shared_ptr
<SvNumberFormatter
> pFormatter
;
519 if (GetSbData()->pInst
)
521 pFormatter
= GetSbData()->pInst
->GetNumberFormatter();
525 sal_uInt32 n
; // Dummy
526 pFormatter
= SbiInstance::PrepareNumberFormatter(n
, n
, n
);
531 sal_Int32 nCheckPos
= 0;
532 SvNumFormatType nType
;
533 OUString aFmtStr
= *pFmt
;
534 if (const VbaFormatInfo
* pInfo
= getFormatInfo(aFmtStr
))
536 if( pInfo
->meType
== VbaFormatType::Offset
)
538 nIndex
= pFormatter
->GetFormatIndex( pInfo
->meOffset
, eLangType
);
542 aFmtStr
= pInfo
->mpOOoFormat
;
543 pFormatter
->PutandConvertEntry( aFmtStr
, nCheckPos
, nType
, nIndex
, LANGUAGE_ENGLISH_US
, eLangType
, true);
545 pFormatter
->GetOutputString( nNumber
, nIndex
, rRes
, &pCol
);
547 else if (aFmtStr
.equalsIgnoreAsciiCase("General Date") // VBA general date variants
548 || aFmtStr
.equalsIgnoreAsciiCase("c"))
551 if( nNumber
<=-1.0 || nNumber
>= 1.0 )
554 nIndex
= pFormatter
->GetFormatIndex( NF_DATE_SYSTEM_SHORT
, eLangType
);
555 pFormatter
->GetOutputString(nNumber
, nIndex
, dateStr
, &pCol
);
557 if (floor(nNumber
) == nNumber
)
564 aFmtStr
= u
"H:MM:SS AM/PM"_ustr
;
565 pFormatter
->PutandConvertEntry(aFmtStr
, nCheckPos
, nType
, nIndex
,
566 LANGUAGE_ENGLISH_US
, eLangType
, true);
567 pFormatter
->GetOutputString(nNumber
, nIndex
, rRes
, &pCol
);
568 if (!dateStr
.isEmpty())
569 rRes
= dateStr
+ " " + rRes
;
571 else if (aFmtStr
.equalsIgnoreAsciiCase("n") // VBA minute variants
572 || aFmtStr
.equalsIgnoreAsciiCase("nn"))
574 sal_Int32 nMin
= implGetMinute( nNumber
);
575 if (nMin
< 10 && aFmtStr
.equalsIgnoreAsciiCase("nn"))
577 // Minute in two digits
580 aBuf
[1] = '0' + nMin
;
581 rRes
= OUString(aBuf
, std::size(aBuf
));
585 rRes
= OUString::number(nMin
);
588 else if (aFmtStr
.equalsIgnoreAsciiCase("w")) // VBA weekday number
590 rRes
= OUString::number(implGetWeekDay(nNumber
));
592 else if (aFmtStr
.equalsIgnoreAsciiCase("y")) // VBA year day number
594 sal_Int16 nYear
= implGetDateYear( nNumber
);
596 implDateSerial( nYear
, 1, 1, true, SbDateCorrection::None
, dBaseDate
);
597 sal_Int32 nYear32
= 1 + sal_Int32( nNumber
- dBaseDate
);
598 rRes
= OUString::number(nYear32
);
602 pFormatter
->PutandConvertEntry( aFmtStr
, nCheckPos
, nType
, nIndex
, LANGUAGE_ENGLISH_US
, eLangType
, true);
603 pFormatter
->GetOutputString( nNumber
, nIndex
, rRes
, &pCol
);
611 SbxDataType eType
= GetType();
615 rRes
= SbxBasicFormater::BasicFormatNull(pFmt
? *pFmt
: std::u16string_view
{});
627 BasicFormatNum(GetDouble(), pFmt
, eType
, rRes
);
630 rRes
= GetOUString();
633 // #45355 converting if numeric
634 if (double d
; ScanNumIntnl(rRes
, d
) == ERRCODE_NONE
)
635 BasicFormatNum(d
, *pFmt
, rRes
);
637 printfmtstr(rRes
, rRes
, *pFmt
);
641 rRes
= GetOUString();
646 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */