/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
 
#include <config_features.h>
 
#include <vcl/errcode.hxx>
#include <unotools/resmgr.hxx>
#include <basic/sbx.hxx>
#include "sbxconv.hxx"
 
#include <unotools/syslocale.hxx>
 
#include <stdlib.h>
 
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
 
#include <math.h>
#include <string.h>
 
#include "sbxres.hxx"
#include <sbxbase.hxx>
#include <sbintern.hxx>
#include <basic/sbxfac.hxx>
#include <basic/sbxform.hxx>
 
#include <date.hxx>
#include <runtime.hxx>
#include <strings.hrc>
 
#include <rtl/strbuf.hxx>
#include <rtl/character.hxx>
#include <sal/log.hxx>
#include <svl/zforlist.hxx>
 
#include <o3tl/make_unique.hxx>
 
 
void ImpGetIntntlSep( sal_Unicode& rcDecimalSep, sal_Unicode& rcThousandSep, sal_Unicode& rcDecimalSepAlt )
{
    SvtSysLocale aSysLocale;
    const LocaleDataWrapper& rData = aSysLocale.GetLocaleData();
    rcDecimalSep = rData.getNumDecimalSep()[0];
    rcThousandSep = rData.getNumThousandSep()[0];
    rcDecimalSepAlt = rData.getNumDecimalSepAlt().toChar();
}
 
 
/** NOTE: slightly differs from strchr() in that it does not consider the
    terminating NULL character to be part of the string and returns bool
    instead of pointer, if character is 0 returns false.
 */
bool ImpStrChr( const sal_Unicode* p, sal_Unicode c )
{
    if (!c)
        return false;
    while (*p)
    {
        if (*p++ == c)
            return true;
    }
    return false;
}
 
 
// scanning a string according to BASIC-conventions
// but exponent may also be a D, so data type is SbxDOUBLE
// conversion error if data type is fixed and it doesn't fit
 
ErrCode ImpScan( const OUString& rWSrc, double& nVal, SbxDataType& rType,
                  sal_uInt16* pLen, bool bOnlyIntntl )
{
    sal_Unicode cIntntlDecSep, cIntntlGrpSep, cIntntlDecSepAlt;
    sal_Unicode cNonIntntlDecSep = '.';
    if( bOnlyIntntl )
    {
        ImpGetIntntlSep( cIntntlDecSep, cIntntlGrpSep, cIntntlDecSepAlt );
        cNonIntntlDecSep = cIntntlDecSep;
        // Ensure that the decimal separator alternative is really one.
        if (cIntntlDecSepAlt && cIntntlDecSepAlt == cNonIntntlDecSep)
            cIntntlDecSepAlt = 0;
    }
    else
    {
        cIntntlDecSep = cNonIntntlDecSep;
        cIntntlGrpSep = 0;  // no group separator accepted in non-i18n
        cIntntlDecSepAlt = 0;
    }
 
    const sal_Unicode* const pStart = rWSrc.getStr();
    const sal_Unicode* p = pStart;
    OUStringBuffer aBuf( rWSrc.getLength());
    bool bRes = true;
    bool bMinus = false;
    nVal = 0;
    SbxDataType eScanType = SbxSINGLE;
    while( *p == ' ' || *p == '\t' )
        p++;
    if( *p == '-' )
    {
        p++;
        bMinus = true;
    }
    if( rtl::isAsciiDigit( *p ) || ((*p == cNonIntntlDecSep || *p == cIntntlDecSep ||
                    (cIntntlDecSep && *p == cIntntlGrpSep) || (cIntntlDecSepAlt && *p == cIntntlDecSepAlt)) &&
                rtl::isAsciiDigit( *(p+1) )))
    {
        // tdf#118442: Whitespace and minus are skipped; store the position to calculate index
        const sal_Unicode* const pDigitsStart = p;
        short exp = 0;
        short decsep = 0;
        short ndig = 0;
        short ncdig = 0;    // number of digits after decimal point
        OUStringBuffer aSearchStr("0123456789DEde");
        aSearchStr.append(cNonIntntlDecSep);
        if( cIntntlDecSep != cNonIntntlDecSep )
            aSearchStr.append(cIntntlDecSep);
        if( cIntntlDecSepAlt && cIntntlDecSepAlt != cNonIntntlDecSep )
            aSearchStr.append(cIntntlDecSepAlt);
        if( bOnlyIntntl )
            aSearchStr.append(cIntntlGrpSep);
        const sal_Unicode* const pSearchStr = aSearchStr.getStr();
        const sal_Unicode pDdEe[] = { 'D', 'd', 'E', 'e', 0 };
        while( ImpStrChr( pSearchStr, *p ) )
        {
            aBuf.append( *p );
            if( bOnlyIntntl && *p == cIntntlGrpSep )
            {
                p++;
                continue;
            }
            if( *p == cNonIntntlDecSep || *p == cIntntlDecSep || (cIntntlDecSepAlt && *p == cIntntlDecSepAlt) )
            {
                // Use the separator that is passed to stringToDouble()
                aBuf[p - pDigitsStart] = cIntntlDecSep;
                p++;
                if( ++decsep > 1 )
                    continue;
            }
            else if( ImpStrChr( pDdEe, *p ) )
            {
                if( ++exp > 1 )
                {
                    p++;
                    continue;
                }
                if( *p == 'D' || *p == 'd' )
                    eScanType = SbxDOUBLE;
                aBuf[p - pDigitsStart] = 'E';
                p++;
                if (*p == '+')
                    ++p;
                else if (*p == '-')
                {
                    aBuf.append('-');
                    ++p;
                }
            }
            else
            {
                p++;
                if( decsep && !exp )
                    ncdig++;
            }
            if( !exp )
                ndig++;
        }
 
        if( decsep > 1 || exp > 1 )
            bRes = false;
 
        OUString aBufStr( aBuf.makeStringAndClear());
        rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
        sal_Int32 nParseEnd = 0;
        nVal = rtl::math::stringToDouble( aBufStr, cIntntlDecSep, cIntntlGrpSep, &eStatus, &nParseEnd );
        if( eStatus != rtl_math_ConversionStatus_Ok || nParseEnd != aBufStr.getLength() )
            bRes = false;
 
        if( !decsep && !exp )
        {
            if( nVal >= SbxMININT && nVal <= SbxMAXINT )
                eScanType = SbxINTEGER;
            else if( nVal >= SbxMINLNG && nVal <= SbxMAXLNG )
                eScanType = SbxLONG;
        }
 
        ndig = ndig - decsep;
        // too many numbers for SINGLE?
        if( ndig > 15 || ncdig > 6 )
            eScanType = SbxDOUBLE;
 
        // type detection?
        const sal_Unicode pTypes[] = { '%', '!', '&', '#', 0 };
        if( ImpStrChr( pTypes, *p ) )
            p++;
    }
    // hex/octal number? read in and convert:
    else if( *p == '&' )
    {
        p++;
        eScanType = SbxLONG;
        OUString aCmp( "0123456789ABCDEFabcdef" );
        char base = 16;
        char ndig = 8;
        switch( *p++ )
        {
            case 'O':
            case 'o':
                aCmp = "01234567";
                base = 8;
                ndig = 11;
                break;
            case 'H':
            case 'h':
                break;
            default :
                bRes = false;
        }
        const sal_Unicode* const pCmp = aCmp.getStr();
        while( rtl::isAsciiAlphanumeric( *p ) )    /* XXX: really munge all alnum also when error? */
        {
            sal_Unicode ch = *p;
            if( ImpStrChr( pCmp, ch ) )
            {
                if (ch > 0x60)
                    ch -= 0x20;     // convert ASCII lower to upper case
                aBuf.append( ch );
            }
            else
                bRes = false;
            p++;
        }
        OUString aBufStr( aBuf.makeStringAndClear());
        sal_Int32 l = 0;
        for( const sal_Unicode* q = aBufStr.getStr(); bRes && *q; q++ )
        {
            int i = *q - '0';
            if( i > 9 )
                i -= 7;     // 'A'-'0' = 17 => 10, ...
            l = ( l * base ) + i;
            if( !ndig-- )
                bRes = false;
        }
        if( *p == '&' )
            p++;
        nVal = static_cast<double>(l);
        if( l >= SbxMININT && l <= SbxMAXINT )
            eScanType = SbxINTEGER;
    }
#if HAVE_FEATURE_SCRIPTING
    else if ( SbiRuntime::isVBAEnabled() )
    {
        SAL_WARN("basic", "Reporting error converting");
        return ERRCODE_BASIC_CONVERSION;
    }
#endif
    if( pLen )
        *pLen = static_cast<sal_uInt16>( p - pStart );
    if( !bRes )
        return ERRCODE_BASIC_CONVERSION;
    if( bMinus )
        nVal = -nVal;
    rType = eScanType;
    return ERRCODE_NONE;
}
 
// port for CDbl in the Basic
ErrCode SbxValue::ScanNumIntnl( const OUString& rSrc, double& nVal, bool bSingle )
{
    SbxDataType t;
    sal_uInt16 nLen = 0;
    ErrCode nRetError = ImpScan( rSrc, nVal, t, &nLen,
        /*bOnlyIntntl*/true );
    // read completely?
    if( nRetError == ERRCODE_NONE && nLen != rSrc.getLength() )
    {
        nRetError = ERRCODE_BASIC_CONVERSION;
    }
    if( bSingle )
    {
        SbxValues aValues( nVal );
        nVal = static_cast<double>(ImpGetSingle( &aValues ));    // here error at overflow
    }
    return nRetError;
}
 
 
static const double roundArray[] = {
    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,
    0.5e-8, 0.5e-9, 0.5e-10,0.5e-11,0.5e-12,0.5e-13,0.5e-14,0.5e-15 };
 
/*
|*
|*  void myftoa( double, char *, short, short, bool, bool )
|*
|*  description:        conversion double --> ASCII
|*  parameters:         double              the number
|*                      char *              target buffer
|*                      short               number of positions after decimal point
|*                      short               range of the exponent ( 0=no E )
|*                      bool                true: with 1000-separators
|*                      bool                true: output without formatting
|*
 */
 
static void myftoa( double nNum, char * pBuf, short nPrec, short nExpWidth,
                    sal_Unicode cForceThousandSep )
{
 
    short nExp = 0;
    short nDig = nPrec + 1;
    short nDec;                         // number of positions before decimal point
    int i;
 
    sal_Unicode cDecimalSep, cThousandSep, cDecimalSepAlt;
    ImpGetIntntlSep( cDecimalSep, cThousandSep, cDecimalSepAlt );
    if( cForceThousandSep )
        cThousandSep = cForceThousandSep;
 
    // compute exponent
    nExp = 0;
    if( nNum > 0.0 )
    {
        while( nNum <   1.0 )
        {
            nNum *= 10.0;
            nExp--;
        }
        while( nNum >= 10.0 )
        {
            nNum /= 10.0;
            nExp++;
        }
    }
    if( !nPrec )
        nDig = nExp + 1;
 
    // round number
    if( (nNum += roundArray [std::min<short>( nDig, 16 )] ) >= 10.0 )
    {
        nNum = 1.0;
        ++nExp;
        if( !nExpWidth ) ++nDig;
    }
 
    // determine positions before decimal point
    if( !nExpWidth )
    {
        if( nExp < 0 )
        {
            // #41691: also a 0 at bFix
            *pBuf++ = '0';
            if( nPrec ) *pBuf++ = static_cast<char>(cDecimalSep);
            i = -nExp - 1;
            if( nDig <= 0 ) i = nPrec;
            while( i-- )    *pBuf++ = '0';
            nDec = 0;
        }
        else
            nDec = nExp+1;
    }
    else
        nDec = 1;
 
    // output number
    if( nDig > 0 )
    {
        int digit;
        for( i = 0 ; ; ++i )
        {
            if( i < 16 )
            {
                digit = static_cast<int>(nNum);
                *pBuf++ = sal::static_int_cast< char >(digit + '0');
                nNum =( nNum - digit ) * 10.0;
            } else
                *pBuf++ = '0';
            if( --nDig == 0 ) break;
            if( nDec )
            {
                nDec--;
                if( !nDec )
                    *pBuf++ = static_cast<char>(cDecimalSep);
            }
        }
    }
 
    // output exponent
    if( nExpWidth )
    {
        if( nExpWidth < 3 ) nExpWidth = 3;
        nExpWidth -= 2;
        *pBuf++ = 'E';
        *pBuf++ =( nExp < 0 ) ?( (nExp = -nExp ), '-' ) : '+';
        while( nExpWidth > 3 )
        {
            *pBuf++ = '0';
            nExpWidth--;
        }
        if( nExp >= 100 || nExpWidth == 3 )
        {
            *pBuf++ = sal::static_int_cast< char >(nExp/100 + '0');
            nExp %= 100;
        }
        if( nExp/10 || nExpWidth >= 2 )
            *pBuf++ = sal::static_int_cast< char >(nExp/10 + '0');
        *pBuf++ = sal::static_int_cast< char >(nExp%10 + '0');
    }
    *pBuf = 0;
}
 
// The number is prepared unformattedly with the given number of
// NK-positions. A leading minus is added if applicable.
// This routine is public because it's also used by the Put-functions
// in the class SbxImpSTRING.
 
void ImpCvtNum( double nNum, short nPrec, OUString& rRes, bool bCoreString )
{
    char *q;
    char cBuf[ 40 ], *p = cBuf;
 
    sal_Unicode cDecimalSep, cThousandSep, cDecimalSepAlt;
    ImpGetIntntlSep( cDecimalSep, cThousandSep, cDecimalSepAlt );
    if( bCoreString )
        cDecimalSep = '.';
 
    if( nNum < 0.0 ) {
        nNum = -nNum;
        *p++ = '-';
    }
    double dMaxNumWithoutExp = (nPrec == 6) ? 1E6 : 1E14;
    myftoa( nNum, p, nPrec,( nNum &&( nNum < 1E-1 || nNum >= dMaxNumWithoutExp ) ) ? 4:0,
        cDecimalSep );
    // remove trailing zeros
    for( p = cBuf; *p &&( *p != 'E' ); p++ ) {}
    q = p; p--;
    while( nPrec && *p == '0' )
    {
        nPrec--;
        p--;
    }
    if( *p == cDecimalSep ) p--;
    while( *q ) *++p = *q++;
    *++p = 0;
    rRes = OUString::createFromAscii( cBuf );
}
 
bool ImpConvStringExt( OUString& rSrc, SbxDataType eTargetType )
{
    bool bChanged = false;
    OUString aNewString;
 
    // only special cases are handled, nothing on default
    switch( eTargetType )
    {
        // consider international for floating point
        case SbxSINGLE:
        case SbxDOUBLE:
        case SbxCURRENCY:
        {
            sal_Unicode cDecimalSep, cThousandSep, cDecimalSepAlt;
            ImpGetIntntlSep( cDecimalSep, cThousandSep, cDecimalSepAlt );
            aNewString = rSrc;
 
            if( cDecimalSep != '.' || (cDecimalSepAlt && cDecimalSepAlt != '.') )
            {
                sal_Int32 nPos = aNewString.indexOf( cDecimalSep );
                if( nPos == -1 && cDecimalSepAlt )
                    nPos = aNewString.indexOf( cDecimalSepAlt );
                if( nPos != -1 )
                {
                    sal_Unicode* pStr = const_cast<sal_Unicode*>(aNewString.getStr());
                    pStr[nPos] = '.';
                    bChanged = true;
                }
            }
            break;
        }
 
        // check as string in case of sal_Bool sal_True and sal_False
        case SbxBOOL:
        {
            if( rSrc.equalsIgnoreAsciiCase("true") )
            {
                aNewString = OUString::number( SbxTRUE );
                bChanged = true;
            }
            else if( rSrc.equalsIgnoreAsciiCase("false") )
            {
                aNewString = OUString::number( SbxFALSE );
                bChanged = true;
            }
            break;
        }
        default: break;
    }
 
    if( bChanged )
        rSrc = aNewString;
    return bChanged;
}
 
 
// formatted number output
// the return value is the number of characters used
// from the format
 
static sal_uInt16 printfmtstr( const OUString& rStr, OUString& rRes, const OUString& rFmt )
{
    OUStringBuffer aTemp;
    const sal_Unicode* pStr = rStr.getStr();
    const sal_Unicode* pFmtStart = rFmt.getStr();
    const sal_Unicode* pFmt = pFmtStart;
 
    switch( *pFmt )
    {
    case '!':
        aTemp.append(*pStr++);
        pFmt++;
        break;
    case '\\':
        do
        {
            aTemp.append( *pStr ? *pStr++ : u' ');
            pFmt++;
        }
        while( *pFmt && *pFmt != '\\' );
        aTemp.append(*pStr ? *pStr++ : u' ');
        pFmt++; break;
    case '&':
        aTemp = rStr;
        pFmt++; break;
    default:
        aTemp = rStr;
        break;
    }
    rRes = aTemp.makeStringAndClear();
    return static_cast<sal_uInt16>( pFmt - pFmtStart );
}
 
 
bool SbxValue::Scan( const OUString& rSrc, sal_uInt16* pLen )
{
    ErrCode eRes = ERRCODE_NONE;
    if( !CanWrite() )
    {
        eRes = ERRCODE_BASIC_PROP_READONLY;
    }
    else
    {
        double n;
        SbxDataType t;
        eRes = ImpScan( rSrc, n, t, pLen, false );
        if( eRes == ERRCODE_NONE )
        {
            if( !IsFixed() )
            {
                SetType( t );
            }
            PutDouble( n );
        }
    }
    if( eRes )
    {
        SetError( eRes );
        return false;
    }
    else
    {
        return true;
    }
}
 
std::locale BasResLocale()
{
    return Translate::Create("sb");
}
 
OUString BasResId(const char *pId)
{
    return Translate::get(pId, BasResLocale());
}
 
namespace
{
 
enum class VbaFormatType
{
    Offset,      // standard number format
    UserDefined, // user defined number format
    Null
};
 
struct VbaFormatInfo
{
    VbaFormatType meType;
    OUStringLiteral mpVbaFormat; // Format string in vba
    NfIndexTableOffset meOffset; // SvNumberFormatter format index, if meType = VbaFormatType::Offset
    const char* mpOOoFormat;     // if meType = VbaFormatType::UserDefined
};
 
#if HAVE_FEATURE_SCRIPTING
const VbaFormatInfo pFormatInfoTable[] =
{
    { VbaFormatType::Offset,      OUStringLiteral("Long Date"),   NF_DATE_SYSTEM_LONG,    nullptr },
    { VbaFormatType::UserDefined, OUStringLiteral("Medium Date"), NF_NUMBER_STANDARD,     "DD-MMM-YY" },
    { VbaFormatType::Offset,      OUStringLiteral("Short Date"),  NF_DATE_SYSTEM_SHORT,   nullptr },
    { VbaFormatType::UserDefined, OUStringLiteral("Long Time"),   NF_NUMBER_STANDARD,     "H:MM:SS AM/PM" },
    { VbaFormatType::Offset,      OUStringLiteral("Medium Time"), NF_TIME_HHMMAMPM,       nullptr },
    { VbaFormatType::Offset,      OUStringLiteral("Short Time"),  NF_TIME_HHMM,           nullptr },
    { VbaFormatType::Offset,      OUStringLiteral("ddddd"),       NF_DATE_SYSTEM_SHORT,   nullptr },
    { VbaFormatType::Offset,      OUStringLiteral("dddddd"),      NF_DATE_SYSTEM_LONG,    nullptr },
    { VbaFormatType::UserDefined, OUStringLiteral("ttttt"),       NF_NUMBER_STANDARD,     "H:MM:SS AM/PM" },
    { VbaFormatType::Offset,      OUStringLiteral("ww"),          NF_DATE_WW,             nullptr },
    { VbaFormatType::Null,        OUStringLiteral(""),            NF_INDEX_TABLE_ENTRIES, nullptr }
};
 
const VbaFormatInfo* getFormatInfo( const OUString& rFmt )
{
    const VbaFormatInfo* pInfo = pFormatInfoTable;
    while( pInfo->meType != VbaFormatType::Null )
    {
        if( rFmt.equalsIgnoreAsciiCase( pInfo->mpVbaFormat ) )
            break;
        ++pInfo;
    }
    return pInfo;
}
#endif
 
} // namespace
 
#if HAVE_FEATURE_SCRIPTING
#define VBAFORMAT_GENERALDATE       "General Date"
#define VBAFORMAT_C                 "c"
#define VBAFORMAT_N                 "n"
#define VBAFORMAT_NN                "nn"
#define VBAFORMAT_W                 "w"
#define VBAFORMAT_Y                 "y"
#define VBAFORMAT_LOWERCASE         "<"
#define VBAFORMAT_UPPERCASE         ">"
#endif
 
void SbxValue::Format( OUString& rRes, const OUString* pFmt ) const
{
    short nComma = 0;
    double d = 0;
 
    // pflin, It is better to use SvNumberFormatter to handle the date/time/number format.
    // the SvNumberFormatter output is mostly compatible with
    // VBA output besides the OOo-basic output
#if HAVE_FEATURE_SCRIPTING
    if( pFmt && !SbxBasicFormater::isBasicFormat( *pFmt ) )
    {
        OUString aStr = GetOUString();
 
        SvtSysLocale aSysLocale;
        const CharClass& rCharClass = aSysLocale.GetCharClass();
 
        if( pFmt->equalsIgnoreAsciiCase( VBAFORMAT_LOWERCASE ) )
        {
            rRes = rCharClass.lowercase( aStr );
            return;
        }
        if( pFmt->equalsIgnoreAsciiCase( VBAFORMAT_UPPERCASE ) )
        {
            rRes = rCharClass.uppercase( aStr );
            return;
        }
 
        LanguageType eLangType = Application::GetSettings().GetLanguageTag().getLanguageType();
        std::shared_ptr<SvNumberFormatter> pFormatter;
        if (GetSbData()->pInst)
        {
            pFormatter = GetSbData()->pInst->GetNumberFormatter();
        }
        else
        {
            sal_uInt32 n;   // Dummy
            pFormatter = SbiInstance::PrepareNumberFormatter( n, n, n );
        }
 
        // Passing an index of a locale switches IsNumberFormat() to use that
        // locale in case the formatter wasn't default created with it.
        sal_uInt32 nIndex = pFormatter->GetStandardIndex( eLangType);
        double nNumber;
        Color* pCol;
 
        bool bSuccess = pFormatter->IsNumberFormat( aStr, nIndex, nNumber );
 
        // number format, use SvNumberFormatter to handle it.
        if( bSuccess )
        {
            sal_Int32 nCheckPos = 0;
            SvNumFormatType nType;
            OUString aFmtStr = *pFmt;
            const VbaFormatInfo* pInfo = getFormatInfo( aFmtStr );
            if( pInfo->meType != VbaFormatType::Null )
            {
                if( pInfo->meType == VbaFormatType::Offset )
                {
                    nIndex = pFormatter->GetFormatIndex( pInfo->meOffset, eLangType );
                }
                else
                {
                    aFmtStr = OUString::createFromAscii(pInfo->mpOOoFormat);
                    pFormatter->PutandConvertEntry( aFmtStr, nCheckPos, nType, nIndex, LANGUAGE_ENGLISH, eLangType, true);
                }
                pFormatter->GetOutputString( nNumber, nIndex, rRes, &pCol );
            }
            else if( aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_GENERALDATE )
                    || aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_C ))
            {
                if( nNumber <=-1.0 || nNumber >= 1.0 )
                {
                    // short date
                    nIndex = pFormatter->GetFormatIndex( NF_DATE_SYSTEM_SHORT, eLangType );
                    pFormatter->GetOutputString( nNumber, nIndex, rRes, &pCol );
 
                    // long time
                    if( floor( nNumber ) != nNumber )
                    {
                        aFmtStr = "H:MM:SS AM/PM";
                        pFormatter->PutandConvertEntry( aFmtStr, nCheckPos, nType, nIndex, LANGUAGE_ENGLISH, eLangType, true);
                        OUString aTime;
                        pFormatter->GetOutputString( nNumber, nIndex, aTime, &pCol );
                        rRes += " " + aTime;
                    }
                }
                else
                {
                    // long time only
                    aFmtStr = "H:MM:SS AM/PM";
                    pFormatter->PutandConvertEntry( aFmtStr, nCheckPos, nType, nIndex, LANGUAGE_ENGLISH, eLangType, true);
                    pFormatter->GetOutputString( nNumber, nIndex, rRes, &pCol );
                }
            }
            else if( aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_N ) ||
                     aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_NN ))
            {
                sal_Int32 nMin = implGetMinute( nNumber );
                if( nMin < 10 && aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_NN ))
                {
                    // Minute in two digits
                     sal_Unicode aBuf[2];
                     aBuf[0] = '0';
                     aBuf[1] = '0' + nMin;
                     rRes = OUString(aBuf, SAL_N_ELEMENTS(aBuf));
                }
                else
                {
                    rRes = OUString::number(nMin);
                }
            }
            else if( aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_W ))
            {
                sal_Int32 nWeekDay = implGetWeekDay( nNumber );
                rRes = OUString::number(nWeekDay);
            }
            else if( aFmtStr.equalsIgnoreAsciiCase( VBAFORMAT_Y ))
            {
                sal_Int16 nYear = implGetDateYear( nNumber );
                double dBaseDate;
                implDateSerial( nYear, 1, 1, true, SbDateCorrection::None, dBaseDate );
                sal_Int32 nYear32 = 1 + sal_Int32( nNumber - dBaseDate );
                rRes = OUString::number(nYear32);
            }
            else
            {
                pFormatter->PutandConvertEntry( aFmtStr, nCheckPos, nType, nIndex, LANGUAGE_ENGLISH, eLangType, true);
                pFormatter->GetOutputString( nNumber, nIndex, rRes, &pCol );
            }
 
            return;
        }
    }
#endif
 
    SbxDataType eType = GetType();
    switch( eType )
    {
    case SbxCHAR:
    case SbxBYTE:
    case SbxINTEGER:
    case SbxUSHORT:
    case SbxLONG:
    case SbxULONG:
    case SbxINT:
    case SbxUINT:
    case SbxNULL:       // #45929 NULL with a little cheating
        nComma = 0;     goto cvt;
    case SbxSINGLE:
        nComma = 6;     goto cvt;
    case SbxDOUBLE:
        nComma = 14;
 
    cvt:
        if( eType != SbxNULL )
        {
            d = GetDouble();
        }
        // #45355 another point to jump in for isnumeric-String
    cvt2:
        if( pFmt )
        {
            SbxAppData& rAppData = GetSbxData_Impl();
 
            LanguageType eLangType = Application::GetSettings().GetLanguageTag().getLanguageType();
            if( rAppData.pBasicFormater )
            {
                if( rAppData.eBasicFormaterLangType != eLangType )
                {
                    rAppData.pBasicFormater.reset();
                }
            }
            rAppData.eBasicFormaterLangType = eLangType;
 
 
            if( !rAppData.pBasicFormater )
            {
                SvtSysLocale aSysLocale;
                const LocaleDataWrapper& rData = aSysLocale.GetLocaleData();
                sal_Unicode cComma = rData.getNumDecimalSep()[0];
                sal_Unicode c1000  = rData.getNumThousandSep()[0];
                const OUString& aCurrencyStrg = rData.getCurrSymbol();
 
                // initialize the Basic-formater help object:
                // get resources for predefined output
                // of the Format()-command, e. g. for "On/Off"
                OUString aOnStrg = BasResId(STR_BASICKEY_FORMAT_ON);
                OUString aOffStrg = BasResId(STR_BASICKEY_FORMAT_OFF);
                OUString aYesStrg = BasResId(STR_BASICKEY_FORMAT_YES);
                OUString aNoStrg = BasResId(STR_BASICKEY_FORMAT_NO);
                OUString aTrueStrg = BasResId(STR_BASICKEY_FORMAT_TRUE);
                OUString aFalseStrg = BasResId(STR_BASICKEY_FORMAT_FALSE);
                OUString aCurrencyFormatStrg = BasResId(STR_BASICKEY_FORMAT_CURRENCY);
 
                rAppData.pBasicFormater = o3tl::make_unique<SbxBasicFormater>(
                                                                cComma,c1000,aOnStrg,aOffStrg,
                                                                aYesStrg,aNoStrg,aTrueStrg,aFalseStrg,
                                                                aCurrencyStrg,aCurrencyFormatStrg );
            }
            // Remark: For performance reasons there's only ONE BasicFormater-
            //    object created and 'stored', so that the expensive resource-
            //    loading is saved (for country-specific predefined outputs,
            //    e. g. "On/Off") and the continuous string-creation
            //    operations, too.
            // BUT: therefore this code is NOT multithreading capable!
 
            // here are problems with ;;;Null because this method is only
            // called, if SbxValue is a number!!!
            // in addition rAppData.pBasicFormater->BasicFormatNull( *pFmt ); could be called!
            if( eType != SbxNULL )
            {
                rRes = rAppData.pBasicFormater->BasicFormat( d ,*pFmt );
            }
            else
            {
                rRes = SbxBasicFormater::BasicFormatNull( *pFmt );
            }
 
        }
        else
        {
            OUString aTmpString( rRes );
            ImpCvtNum( GetDouble(), nComma, aTmpString );
            rRes = aTmpString;
        }
        break;
    case SbxSTRING:
        if( pFmt )
        {
            // #45355 converting if numeric
            if( IsNumericRTL() )
            {
                ScanNumIntnl( GetOUString(), d );
                goto cvt2;
            }
            else
            {
                printfmtstr( GetOUString(), rRes, *pFmt );
            }
        }
        else
        {
            rRes = GetOUString();
        }
        break;
    default:
        rRes = GetOUString();
    }
}
 
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V793 It is odd that the result of the 'nExp / 10' statement is a part of the condition. Perhaps, this statement should have been compared with something else.