summaryrefslogtreecommitdiffstats
path: root/basic/source/runtime/methods.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'basic/source/runtime/methods.cxx')
-rw-r--r--basic/source/runtime/methods.cxx4745
1 files changed, 4745 insertions, 0 deletions
diff --git a/basic/source/runtime/methods.cxx b/basic/source/runtime/methods.cxx
new file mode 100644
index 0000000000..ad3b638805
--- /dev/null
+++ b/basic/source/runtime/methods.cxx
@@ -0,0 +1,4745 @@
+/* -*- 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 <tools/date.hxx>
+#include <basic/sbxvar.hxx>
+#include <basic/sbuno.hxx>
+#include <osl/process.h>
+#include <vcl/dibtools.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/sound.hxx>
+#include <vcl/wintypes.hxx>
+#include <vcl/stdtext.hxx>
+#include <vcl/weld.hxx>
+#include <basic/sbx.hxx>
+#include <svl/zforlist.hxx>
+#include <rtl/character.hxx>
+#include <rtl/math.hxx>
+#include <tools/urlobj.hxx>
+#include <osl/time.h>
+#include <unotools/charclass.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <unotools/wincodepage.hxx>
+#include <tools/wldcrd.hxx>
+#include <i18nlangtag/lang.h>
+#include <rtl/string.hxx>
+#include <sal/log.hxx>
+#include <comphelper/DirectoryHelper.hxx>
+
+#include <runtime.hxx>
+#include <sbunoobj.hxx>
+#include <osl/file.hxx>
+#include <errobject.hxx>
+
+#include <comphelper/string.hxx>
+#include <comphelper/processfactory.hxx>
+
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/util/DateTime.hpp>
+#include <com/sun/star/lang/Locale.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/ucb/SimpleFileAccess.hpp>
+#include <com/sun/star/script/XErrorQuery.hpp>
+#include <ooo/vba/VbStrConv.hpp>
+#include <ooo/vba/VbTriState.hpp>
+#include <com/sun/star/bridge/oleautomation/XAutomationObject.hpp>
+#include <memory>
+#include <random>
+#include <string_view>
+#include <o3tl/char16_t2wchar_t.hxx>
+
+// include search util
+#include <com/sun/star/i18n/Transliteration.hpp>
+#include <com/sun/star/util/SearchAlgorithms2.hpp>
+#include <i18nutil/searchopt.hxx>
+#include <unotools/textsearch.hxx>
+#include <svl/numformat.hxx>
+
+#include <date.hxx>
+#include <sbstdobj.hxx>
+#include <rtlproto.hxx>
+#include <image.hxx>
+#include <iosys.hxx>
+#include "ddectrl.hxx"
+#include <sbintern.hxx>
+#include <basic/vbahelper.hxx>
+
+#include <vector>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <sbobjmod.hxx>
+#include <sbxmod.hxx>
+
+#ifdef _WIN32
+#include <prewin.h>
+#include <direct.h>
+#include <io.h>
+#include <postwin.h>
+#else
+#include <unistd.h>
+#endif
+
+#include <vcl/TypeSerializer.hxx>
+
+using namespace comphelper;
+using namespace osl;
+using namespace com::sun::star;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+
+static sal_Int32 GetDayDiff(const Date& rDate) { return rDate - Date(1899'12'30); }
+
+#if HAVE_FEATURE_SCRIPTING
+
+static void FilterWhiteSpace( OUString& rStr )
+{
+ if (rStr.isEmpty())
+ {
+ return;
+ }
+ OUStringBuffer aRet;
+
+ for (sal_Int32 i = 0; i < rStr.getLength(); ++i)
+ {
+ sal_Unicode cChar = rStr[i];
+ if ((cChar != ' ') && (cChar != '\t') &&
+ (cChar != '\n') && (cChar != '\r'))
+ {
+ aRet.append(cChar);
+ }
+ }
+
+ rStr = aRet.makeStringAndClear();
+}
+
+static const CharClass& GetCharClass()
+{
+ static CharClass aCharClass( Application::GetSettings().GetLanguageTag() );
+ return aCharClass;
+}
+
+static bool isFolder( FileStatus::Type aType )
+{
+ return ( aType == FileStatus::Directory || aType == FileStatus::Volume );
+}
+
+
+//*** UCB file access ***
+
+// Converts possibly relative paths to absolute paths
+// according to the setting done by ChDir/ChDrive
+OUString getFullPath( const OUString& aRelPath )
+{
+ OUString aFileURL;
+
+ // #80204 Try first if it already is a valid URL
+ INetURLObject aURLObj( aRelPath );
+ aFileURL = aURLObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ if( aFileURL.isEmpty() )
+ {
+ File::getFileURLFromSystemPath( aRelPath, aFileURL );
+ }
+
+ return aFileURL;
+}
+
+// TODO: -> SbiGlobals
+static uno::Reference< ucb::XSimpleFileAccess3 > const & getFileAccess()
+{
+ static uno::Reference< ucb::XSimpleFileAccess3 > xSFI = ucb::SimpleFileAccess::create( comphelper::getProcessComponentContext() );
+ return xSFI;
+}
+
+
+// Properties and methods lie down the return value at the Get (bPut = sal_False) in the
+// element 0 of the Argv; the value of element 0 is saved at Put (bPut = sal_True)
+
+// CreateObject( class )
+
+void SbRtl_CreateObject(StarBASIC * pBasic, SbxArray & rPar, bool)
+{
+ if( rPar.Count() < 2 )
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ OUString aClass(rPar.Get(1)->GetOUString());
+ SbxObjectRef p = SbxBase::CreateObject( aClass );
+ if( !p.is() )
+ return StarBASIC::Error( ERRCODE_BASIC_CANNOT_LOAD );
+
+ // Convenience: enter BASIC as parent
+ p->SetParent( pBasic );
+ rPar.Get(0)->PutObject(p.get());
+}
+
+// Error( n )
+
+void SbRtl_Error(StarBASIC * pBasic, SbxArray & rPar, bool)
+{
+ if( !pBasic )
+ return StarBASIC::Error( ERRCODE_BASIC_INTERNAL_ERROR );
+
+ OUString aErrorMsg;
+ ErrCode nErr = ERRCODE_NONE;
+ sal_Int32 nCode = 0;
+ if (rPar.Count() == 1)
+ {
+ nErr = StarBASIC::GetErrBasic();
+ aErrorMsg = StarBASIC::GetErrorMsg();
+ }
+ else
+ {
+ nCode = rPar.Get(1)->GetLong();
+ if( nCode > 65535 )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_CONVERSION );
+ }
+ else
+ {
+ nErr = StarBASIC::GetSfxFromVBError( static_cast<sal_uInt16>(nCode) );
+ }
+ }
+ bool bVBA = SbiRuntime::isVBAEnabled();
+ OUString tmpErrMsg;
+ if( bVBA && !aErrorMsg.isEmpty())
+ {
+ tmpErrMsg = aErrorMsg;
+ }
+ else
+ {
+ StarBASIC::MakeErrorText( nErr, aErrorMsg );
+ tmpErrMsg = StarBASIC::GetErrorText();
+ }
+ // If this rtlfunc 'Error' passed an errcode the same as the active Err Objects's
+ // current err then return the description for the error message if it is set
+ // ( complicated isn't it ? )
+ if (bVBA && rPar.Count() > 1)
+ {
+ uno::Reference< ooo::vba::XErrObject > xErrObj( SbxErrObject::getUnoErrObject() );
+ if ( xErrObj.is() && xErrObj->getNumber() == nCode && !xErrObj->getDescription().isEmpty() )
+ {
+ tmpErrMsg = xErrObj->getDescription();
+ }
+ }
+ rPar.Get(0)->PutString(tmpErrMsg);
+}
+
+// Sinus
+
+void SbRtl_Sin(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ SbxVariableRef pArg = rPar.Get(1);
+ rPar.Get(0)->PutDouble(sin(pArg->GetDouble()));
+}
+
+
+void SbRtl_Cos(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ SbxVariableRef pArg = rPar.Get(1);
+ rPar.Get(0)->PutDouble(cos(pArg->GetDouble()));
+}
+
+
+void SbRtl_Atn(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ SbxVariableRef pArg = rPar.Get(1);
+ rPar.Get(0)->PutDouble(atan(pArg->GetDouble()));
+}
+
+
+void SbRtl_Abs(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ SbxVariableRef pArg = rPar.Get(1);
+ rPar.Get(0)->PutDouble(fabs(pArg->GetDouble()));
+}
+
+
+void SbRtl_Asc(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ SbxVariableRef pArg = rPar.Get(1);
+ OUString aStr( pArg->GetOUString() );
+ if ( aStr.isEmpty())
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ rPar.Get(0)->PutEmpty();
+ return;
+ }
+ sal_Unicode aCh = aStr[0];
+ rPar.Get(0)->PutLong(aCh);
+}
+
+static void implChr( SbxArray& rPar, bool bChrW )
+{
+ if (rPar.Count() < 2)
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ SbxVariableRef pArg = rPar.Get(1);
+
+ OUString aStr;
+ if( !bChrW && SbiRuntime::isVBAEnabled() )
+ {
+ char c = static_cast<char>(pArg->GetByte());
+ aStr = OUString(&c, 1, osl_getThreadTextEncoding());
+ }
+ else
+ {
+ // Map negative 16-bit values to large positive ones, so that code like Chr(&H8000)
+ // still works after the fix for tdf#62326 changed those four-digit hex notations to
+ // produce negative values:
+ sal_Int32 aCh = pArg->GetLong();
+ if (aCh < -0x8000 || aCh > 0xFFFF)
+ {
+ StarBASIC::Error(ERRCODE_BASIC_MATH_OVERFLOW);
+ aCh = 0;
+ }
+ aStr = OUString(static_cast<sal_Unicode>(aCh));
+ }
+ rPar.Get(0)->PutString(aStr);
+}
+
+void SbRtl_Chr(StarBASIC *, SbxArray & rPar, bool)
+{
+ implChr( rPar, false/*bChrW*/ );
+}
+
+void SbRtl_ChrW(StarBASIC *, SbxArray & rPar, bool)
+{
+ implChr( rPar, true/*bChrW*/ );
+}
+
+#if defined _WIN32
+
+namespace {
+
+extern "C" void invalidParameterHandler(
+ wchar_t const * expression, wchar_t const * function, wchar_t const * file, unsigned int line,
+ uintptr_t)
+{
+ SAL_INFO(
+ "basic",
+ "invalid parameter during _wgetdcwd; \""
+ << (expression ? OUString(o3tl::toU(expression)) : OUString("???"))
+ << "\" (" << (function ? OUString(o3tl::toU(function)) : OUString("???")) << ") at "
+ << (file ? OUString(o3tl::toU(file)) : OUString("???")) << ":" << line);
+}
+
+}
+
+#endif
+
+void SbRtl_CurDir(StarBASIC *, SbxArray & rPar, bool)
+{
+ // #57064 Although this function doesn't work with DirEntry, it isn't touched
+ // by the adjustment to virtual URLs, as, using the DirEntry-functionality,
+ // there's no possibility to detect the current one in a way that a virtual URL
+ // could be delivered.
+
+#if defined(_WIN32)
+ int nCurDir = 0; // Current dir // JSM
+ if (rPar.Count() == 2)
+ {
+ OUString aDrive = rPar.Get(1)->GetOUString();
+ if ( aDrive.getLength() != 1 )
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ auto c = rtl::toAsciiUpperCase(aDrive[0]);
+ if ( !rtl::isAsciiUpperCase( c ) )
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ nCurDir = c - 'A' + 1;
+ }
+ wchar_t pBuffer[ _MAX_PATH ];
+ // _wgetdcwd calls the C runtime's invalid parameter handler (which by default terminates the
+ // process) if nCurDir does not correspond to an existing drive, so temporarily set a "harmless"
+ // handler:
+ auto const handler = _set_thread_local_invalid_parameter_handler(&invalidParameterHandler);
+ auto const ok = _wgetdcwd( nCurDir, pBuffer, _MAX_PATH ) != nullptr;
+ _set_thread_local_invalid_parameter_handler(handler);
+ if ( !ok )
+ return StarBASIC::Error( ERRCODE_BASIC_NO_DEVICE );
+
+ rPar.Get(0)->PutString(OUString(o3tl::toU(pBuffer)));
+
+#else
+
+ const int PATH_INCR = 250;
+
+ int nSize = PATH_INCR;
+ std::unique_ptr<char[]> pMem;
+ while( true )
+ {
+ pMem.reset(new char[nSize]);
+ if( !pMem )
+ return StarBASIC::Error( ERRCODE_BASIC_NO_MEMORY );
+
+ if( getcwd( pMem.get(), nSize-1 ) != nullptr )
+ {
+ rPar.Get(0)->PutString(OUString::createFromAscii(pMem.get()));
+ return;
+ }
+ if( errno != ERANGE )
+ return StarBASIC::Error( ERRCODE_BASIC_INTERNAL_ERROR );
+
+ nSize += PATH_INCR;
+ };
+
+#endif
+}
+
+void SbRtl_ChDir(StarBASIC * pBasic, SbxArray & rPar, bool)
+{
+ rPar.Get(0)->PutEmpty();
+ if (rPar.Count() == 2)
+ {
+ // VBA: track current directory per document type (separately for Writer, Calc, Impress, etc.)
+ if( SbiRuntime::isVBAEnabled() )
+ {
+ ::basic::vba::registerCurrentDirectory(getDocumentModel(pBasic),
+ rPar.Get(1)->GetOUString());
+ }
+ }
+ else
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+}
+
+void SbRtl_ChDrive(StarBASIC *, SbxArray & rPar, bool)
+{
+ rPar.Get(0)->PutEmpty();
+ if (rPar.Count() != 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+}
+
+
+// Implementation of StepRENAME with UCB
+void implStepRenameUCB( const OUString& aSource, const OUString& aDest )
+{
+ const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
+ if( !xSFI.is() )
+ return;
+
+ try
+ {
+ OUString aSourceFullPath = getFullPath( aSource );
+ if( !xSFI->exists( aSourceFullPath ) )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
+ return;
+ }
+
+ OUString aDestFullPath = getFullPath( aDest );
+ if( xSFI->exists( aDestFullPath ) )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_FILE_EXISTS );
+ }
+ else
+ {
+ xSFI->move( aSourceFullPath, aDestFullPath );
+ }
+ }
+ catch(const Exception & )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
+ }
+}
+
+// Implementation of StepRENAME with OSL
+void implStepRenameOSL( const OUString& aSource, const OUString& aDest )
+{
+ FileBase::RC nRet = File::move( getFullPath( aSource ), getFullPath( aDest ) );
+ if( nRet != FileBase::E_None )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
+ }
+}
+
+void SbRtl_FileCopy(StarBASIC *, SbxArray & rPar, bool)
+{
+ rPar.Get(0)->PutEmpty();
+ if (rPar.Count() == 3)
+ {
+ OUString aSource = rPar.Get(1)->GetOUString();
+ OUString aDest = rPar.Get(2)->GetOUString();
+ if( hasUno() )
+ {
+ const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
+ if( xSFI.is() )
+ {
+ try
+ {
+ xSFI->copy( getFullPath( aSource ), getFullPath( aDest ) );
+ }
+ catch(const Exception & )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
+ }
+ }
+ }
+ else
+ {
+ FileBase::RC nRet = File::copy( getFullPath( aSource ), getFullPath( aDest ) );
+ if( nRet != FileBase::E_None )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
+ }
+ }
+ }
+ else
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+}
+
+void SbRtl_Kill(StarBASIC *, SbxArray & rPar, bool)
+{
+ rPar.Get(0)->PutEmpty();
+ if (rPar.Count() == 2)
+ {
+ OUString aFileSpec = rPar.Get(1)->GetOUString();
+
+ if( hasUno() )
+ {
+ const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
+ if( xSFI.is() )
+ {
+ OUString aFullPath = getFullPath( aFileSpec );
+ if( !xSFI->exists( aFullPath ) || xSFI->isFolder( aFullPath ) )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
+ return;
+ }
+ try
+ {
+ xSFI->kill( aFullPath );
+ }
+ catch(const Exception & )
+ {
+ StarBASIC::Error( ERRCODE_IO_GENERAL );
+ }
+ }
+ }
+ else
+ {
+ File::remove( getFullPath( aFileSpec ) );
+ }
+ }
+ else
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+}
+
+void SbRtl_MkDir(StarBASIC * pBasic, SbxArray & rPar, bool bWrite)
+{
+ rPar.Get(0)->PutEmpty();
+ if (rPar.Count() == 2)
+ {
+ OUString aPath = rPar.Get(1)->GetOUString();
+ if ( SbiRuntime::isVBAEnabled() )
+ {
+ // In vba if the full path is not specified then
+ // folder is created relative to the curdir
+ INetURLObject aURLObj( getFullPath( aPath ) );
+ if ( aURLObj.GetProtocol() != INetProtocol::File )
+ {
+ SbxArrayRef pPar = new SbxArray();
+ SbxVariableRef pResult = new SbxVariable();
+ SbxVariableRef pParam = new SbxVariable();
+ pPar->Insert(pResult.get(), pPar->Count());
+ pPar->Insert(pParam.get(), pPar->Count());
+ SbRtl_CurDir( pBasic, *pPar, bWrite );
+
+ OUString sCurPathURL;
+ File::getFileURLFromSystemPath(pPar->Get(0)->GetOUString(), sCurPathURL);
+
+ aURLObj.SetURL( sCurPathURL );
+ aURLObj.Append( aPath );
+ File::getSystemPathFromFileURL(aURLObj.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ),aPath ) ;
+ }
+ }
+
+ if( hasUno() )
+ {
+ const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
+ if( xSFI.is() )
+ {
+ try
+ {
+ xSFI->createFolder( getFullPath( aPath ) );
+ }
+ catch(const Exception & )
+ {
+ StarBASIC::Error( ERRCODE_IO_GENERAL );
+ }
+ }
+ }
+ else
+ {
+ Directory::create( getFullPath( aPath ) );
+ }
+ }
+ else
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+}
+
+
+static void implRemoveDirRecursive( const OUString& aDirPath )
+{
+ DirectoryItem aItem;
+ FileBase::RC nRet = DirectoryItem::get( aDirPath, aItem );
+ bool bExists = (nRet == FileBase::E_None);
+
+ FileStatus aFileStatus( osl_FileStatus_Mask_Type );
+ nRet = aItem.getFileStatus( aFileStatus );
+ bool bFolder = nRet == FileBase::E_None
+ && isFolder( aFileStatus.getFileType() );
+
+ if( !bExists || !bFolder )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
+ }
+
+ Directory aDir( aDirPath );
+ nRet = aDir.open();
+ if( nRet != FileBase::E_None )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
+ }
+ aDir.close();
+
+ comphelper::DirectoryHelper::deleteDirRecursively(aDirPath);
+}
+
+
+void SbRtl_RmDir(StarBASIC *, SbxArray & rPar, bool)
+{
+ rPar.Get(0)->PutEmpty();
+ if (rPar.Count() == 2)
+ {
+ OUString aPath = rPar.Get(1)->GetOUString();
+ if( hasUno() )
+ {
+ const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
+ if( xSFI.is() )
+ {
+ try
+ {
+ if( !xSFI->isFolder( aPath ) )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
+ }
+ SbiInstance* pInst = GetSbData()->pInst;
+ bool bCompatibility = ( pInst && pInst->IsCompatibility() );
+ if( bCompatibility )
+ {
+ Sequence< OUString > aContent = xSFI->getFolderContents( aPath, true );
+ if( aContent.hasElements() )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_ACCESS_ERROR );
+ }
+ }
+
+ xSFI->kill( getFullPath( aPath ) );
+ }
+ catch(const Exception & )
+ {
+ StarBASIC::Error( ERRCODE_IO_GENERAL );
+ }
+ }
+ }
+ else
+ {
+ implRemoveDirRecursive( getFullPath( aPath ) );
+ }
+ }
+ else
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+}
+
+void SbRtl_SendKeys(StarBASIC *, SbxArray & rPar, bool)
+{
+ rPar.Get(0)->PutEmpty();
+ StarBASIC::Error(ERRCODE_BASIC_NOT_IMPLEMENTED);
+}
+
+void SbRtl_Exp(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ double aDouble = rPar.Get(1)->GetDouble();
+ aDouble = exp( aDouble );
+ checkArithmeticOverflow( aDouble );
+ rPar.Get(0)->PutDouble(aDouble);
+}
+
+void SbRtl_FileLen(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ SbxVariableRef pArg = rPar.Get(1);
+ OUString aStr( pArg->GetOUString() );
+ sal_Int32 nLen = 0;
+ if( hasUno() )
+ {
+ const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
+ if( xSFI.is() )
+ {
+ try
+ {
+ nLen = xSFI->getSize( getFullPath( aStr ) );
+ }
+ catch(const Exception & )
+ {
+ StarBASIC::Error( ERRCODE_IO_GENERAL );
+ }
+ }
+ }
+ else
+ {
+ DirectoryItem aItem;
+ (void)DirectoryItem::get( getFullPath( aStr ), aItem );
+ FileStatus aFileStatus( osl_FileStatus_Mask_FileSize );
+ (void)aItem.getFileStatus( aFileStatus );
+ nLen = static_cast<sal_Int32>(aFileStatus.getFileSize());
+ }
+ rPar.Get(0)->PutLong(nLen);
+}
+
+
+
+void SbRtl_Hex(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ SbxVariableRef pArg = rPar.Get(1);
+ // converting value to unsigned and limit to 2 or 4 byte representation
+ sal_uInt32 nVal = pArg->IsInteger() ?
+ static_cast<sal_uInt16>(pArg->GetInteger()) :
+ static_cast<sal_uInt32>(pArg->GetLong());
+ rPar.Get(0)->PutString(OUString::number(nVal, 16).toAsciiUpperCase());
+}
+
+void SbRtl_FuncCaller(StarBASIC *, SbxArray & rPar, bool)
+{
+ if ( SbiRuntime::isVBAEnabled() && GetSbData()->pInst && GetSbData()->pInst->pRun )
+ {
+ if ( GetSbData()->pInst->pRun->GetExternalCaller() )
+ *rPar.Get(0) = *GetSbData()->pInst->pRun->GetExternalCaller();
+ else
+ {
+ SbxVariableRef pVar = new SbxVariable(SbxVARIANT);
+ *rPar.Get(0) = *pVar;
+ }
+ }
+ else
+ {
+ StarBASIC::Error( ERRCODE_BASIC_NOT_IMPLEMENTED );
+ }
+
+}
+// InStr( [start],string,string,[compare] )
+
+void SbRtl_InStr(StarBASIC *, SbxArray & rPar, bool)
+{
+ const sal_uInt32 nArgCount = rPar.Count() - 1;
+ if ( nArgCount < 2 )
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ else
+ {
+ sal_Int32 nStartPos = 1;
+ sal_Int32 nFirstStringPos = 1;
+
+ if ( nArgCount >= 3 )
+ {
+ nStartPos = rPar.Get(1)->GetLong();
+ if( nStartPos <= 0 )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ nStartPos = 1;
+ }
+ nFirstStringPos++;
+ }
+
+ SbiInstance* pInst = GetSbData()->pInst;
+ bool bTextMode;
+ bool bCompatibility = ( pInst && pInst->IsCompatibility() );
+ if( bCompatibility )
+ {
+ SbiRuntime* pRT = pInst->pRun;
+ bTextMode = pRT && pRT->IsImageFlag( SbiImageFlags::COMPARETEXT );
+ }
+ else
+ {
+ bTextMode = true;
+ }
+ if ( nArgCount == 4 )
+ {
+ bTextMode = rPar.Get(4)->GetInteger();
+ }
+ sal_Int32 nPos;
+ const OUString& rToken = rPar.Get(nFirstStringPos + 1)->GetOUString();
+
+ // #97545 Always find empty string
+ if( rToken.isEmpty() )
+ {
+ nPos = nStartPos;
+ }
+ else
+ {
+ const OUString& rStr1 = rPar.Get(nFirstStringPos)->GetOUString();
+ const sal_Int32 nrStr1Len = rStr1.getLength();
+ if (nStartPos > nrStr1Len)
+ {
+ // Start position is greater than the string being searched
+ nPos = 0;
+ }
+ else
+ {
+ if( !bTextMode )
+ {
+ nPos = rStr1.indexOf( rToken, nStartPos - 1 ) + 1;
+ }
+ else
+ {
+ // tdf#139840 - case-insensitive operation for non-ASCII characters
+ i18nutil::SearchOptions2 aSearchOptions;
+ aSearchOptions.searchString = rToken;
+ aSearchOptions.AlgorithmType2 = util::SearchAlgorithms2::ABSOLUTE;
+ aSearchOptions.transliterateFlags |= TransliterationFlags::IGNORE_CASE;
+ utl::TextSearch textSearch(aSearchOptions);
+
+ sal_Int32 nStart = nStartPos - 1;
+ sal_Int32 nEnd = nrStr1Len;
+ nPos = textSearch.SearchForward(rStr1, &nStart, &nEnd) ? nStart + 1 : 0;
+ }
+ }
+ }
+ rPar.Get(0)->PutLong(nPos);
+ }
+}
+
+
+// InstrRev(string1, string2[, start[, compare]])
+
+void SbRtl_InStrRev(StarBASIC *, SbxArray & rPar, bool)
+{
+ const sal_uInt32 nArgCount = rPar.Count() - 1;
+ if ( nArgCount < 2 )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ const OUString aStr1 = rPar.Get(1)->GetOUString();
+ const OUString aToken = rPar.Get(2)->GetOUString();
+
+ sal_Int32 nStartPos = -1;
+ if ( nArgCount >= 3 )
+ {
+ nStartPos = rPar.Get(3)->GetLong();
+ if( nStartPos <= 0 && nStartPos != -1 )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ nStartPos = -1;
+ }
+ }
+
+ SbiInstance* pInst = GetSbData()->pInst;
+ bool bTextMode;
+ bool bCompatibility = ( pInst && pInst->IsCompatibility() );
+ if( bCompatibility )
+ {
+ SbiRuntime* pRT = pInst->pRun;
+ bTextMode = pRT && pRT->IsImageFlag( SbiImageFlags::COMPARETEXT );
+ }
+ else
+ {
+ bTextMode = true;
+ }
+ if ( nArgCount == 4 )
+ {
+ bTextMode = rPar.Get(4)->GetInteger();
+ }
+ const sal_Int32 nStrLen = aStr1.getLength();
+ if( nStartPos == -1 )
+ {
+ nStartPos = nStrLen;
+ }
+
+ sal_Int32 nPos = 0;
+ if( nStartPos <= nStrLen )
+ {
+ sal_Int32 nTokenLen = aToken.getLength();
+ if( !nTokenLen )
+ {
+ // Always find empty string
+ nPos = nStartPos;
+ }
+ else if( nStrLen > 0 )
+ {
+ if( !bTextMode )
+ {
+ nPos = aStr1.lastIndexOf( aToken, nStartPos ) + 1;
+ }
+ else
+ {
+ // tdf#143332 - case-insensitive operation for non-ASCII characters
+ i18nutil::SearchOptions2 aSearchOptions;
+ aSearchOptions.searchString = aToken;
+ aSearchOptions.AlgorithmType2 = util::SearchAlgorithms2::ABSOLUTE;
+ aSearchOptions.transliterateFlags |= TransliterationFlags::IGNORE_CASE;
+ utl::TextSearch textSearch(aSearchOptions);
+
+ sal_Int32 nStart = 0;
+ sal_Int32 nEnd = nStartPos;
+ nPos = textSearch.SearchBackward(aStr1, &nEnd, &nStart) ? nStart : 0;
+ }
+ }
+ }
+ rPar.Get(0)->PutLong(nPos);
+}
+
+
+/*
+ Int( 2.8 ) = 2.0
+ Int( -2.8 ) = -3.0
+ Fix( 2.8 ) = 2.0
+ Fix( -2.8 ) = -2.0 <- !!
+*/
+
+void SbRtl_Int(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ SbxVariableRef pArg = rPar.Get(1);
+ double aDouble= pArg->GetDouble();
+ /*
+ floor( 2.8 ) = 2.0
+ floor( -2.8 ) = -3.0
+ */
+ aDouble = floor( aDouble );
+ rPar.Get(0)->PutDouble(aDouble);
+}
+
+
+void SbRtl_Fix(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ SbxVariableRef pArg = rPar.Get(1);
+ double aDouble = pArg->GetDouble();
+ if ( aDouble >= 0.0 )
+ aDouble = floor( aDouble );
+ else
+ aDouble = ceil( aDouble );
+ rPar.Get(0)->PutDouble(aDouble);
+}
+
+
+void SbRtl_LCase(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ const CharClass& rCharClass = GetCharClass();
+ OUString aStr(rPar.Get(1)->GetOUString());
+ aStr = rCharClass.lowercase(aStr);
+ rPar.Get(0)->PutString(aStr);
+}
+
+void SbRtl_Left(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 3)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ OUString aStr(rPar.Get(1)->GetOUString());
+ sal_Int32 nResultLen = rPar.Get(2)->GetLong();
+ if( nResultLen < 0 )
+ {
+ nResultLen = 0;
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else if(nResultLen > aStr.getLength())
+ {
+ nResultLen = aStr.getLength();
+ }
+ aStr = aStr.copy(0, nResultLen );
+ rPar.Get(0)->PutString(aStr);
+ }
+}
+
+void SbRtl_Log(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ double aArg = rPar.Get(1)->GetDouble();
+ if ( aArg > 0 )
+ {
+ double d = log( aArg );
+ checkArithmeticOverflow( d );
+ rPar.Get(0)->PutDouble(d);
+ }
+ else
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ }
+}
+
+void SbRtl_LTrim(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ OUString aStr(comphelper::string::stripStart(rPar.Get(1)->GetOUString(), ' '));
+ rPar.Get(0)->PutString(aStr);
+}
+
+
+// Mid( String, nStart, nLength )
+
+void SbRtl_Mid(StarBASIC *, SbxArray & rPar, bool bWrite)
+{
+ int nArgCount = rPar.Count() - 1;
+ if ( nArgCount < 2 )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ // #23178: replicate the functionality of Mid$ as a command
+ // by adding a replacement-string as a fourth parameter.
+ // In contrast to the original the third parameter (nLength)
+ // can't be left out here. That's considered in bWrite already.
+ if( nArgCount == 4 )
+ {
+ bWrite = true;
+ }
+ OUString aArgStr = rPar.Get(1)->GetOUString();
+ sal_Int32 nStartPos = rPar.Get(2)->GetLong();
+ if ( nStartPos < 1 )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ nStartPos--;
+ sal_Int32 nLen = -1;
+ bool bWriteNoLenParam = false;
+ if ( nArgCount == 3 || bWrite )
+ {
+ sal_Int32 n = rPar.Get(3)->GetLong();
+ if( bWrite && n == -1 )
+ {
+ bWriteNoLenParam = true;
+ }
+ nLen = n;
+ }
+ if ( bWrite )
+ {
+ sal_Int32 nArgLen = aArgStr.getLength();
+ if( nStartPos > nArgLen )
+ {
+ SbiInstance* pInst = GetSbData()->pInst;
+ bool bCompatibility = ( pInst && pInst->IsCompatibility() );
+ if( bCompatibility )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ nStartPos = nArgLen;
+ }
+
+ OUString aReplaceStr = rPar.Get(4)->GetOUString();
+ sal_Int32 nReplaceStrLen = aReplaceStr.getLength();
+ sal_Int32 nReplaceLen;
+ if( bWriteNoLenParam )
+ {
+ nReplaceLen = nArgLen - nStartPos;
+ }
+ else
+ {
+ nReplaceLen = nLen;
+ if( nReplaceLen < 0 || nReplaceLen > nArgLen - nStartPos )
+ {
+ nReplaceLen = nArgLen - nStartPos;
+ }
+ }
+
+ OUStringBuffer aResultStr(aArgStr);
+ sal_Int32 nErase = nReplaceLen;
+ aResultStr.remove( nStartPos, nErase );
+ aResultStr.insert(
+ nStartPos, aReplaceStr.getStr(), std::min(nReplaceLen, nReplaceStrLen));
+
+ rPar.Get(1)->PutString(aResultStr.makeStringAndClear());
+ }
+ else
+ {
+ OUString aResultStr;
+ if (nStartPos > aArgStr.getLength())
+ {
+ // do nothing
+ }
+ else if(nArgCount == 2)
+ {
+ aResultStr = aArgStr.copy( nStartPos);
+ }
+ else
+ {
+ if (nLen < 0)
+ nLen = 0;
+ if(nStartPos + nLen > aArgStr.getLength())
+ {
+ nLen = aArgStr.getLength() - nStartPos;
+ }
+ if (nLen > 0)
+ aResultStr = aArgStr.copy( nStartPos, nLen );
+ }
+ rPar.Get(0)->PutString(aResultStr);
+ }
+ }
+ }
+}
+
+void SbRtl_Oct(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ SbxVariableRef pArg = rPar.Get(1);
+ // converting value to unsigned and limit to 2 or 4 byte representation
+ sal_uInt32 nVal = pArg->IsInteger() ?
+ static_cast<sal_uInt16>(pArg->GetInteger()) :
+ static_cast<sal_uInt32>(pArg->GetLong());
+ rPar.Get(0)->PutString(OUString::number(nVal, 8));
+ }
+}
+
+// Replace(expression, find, replace[, start[, count[, compare]]])
+
+void SbRtl_Replace(StarBASIC *, SbxArray & rPar, bool)
+{
+ const sal_uInt32 nArgCount = rPar.Count() - 1;
+ if ( nArgCount < 3 || nArgCount > 6 )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ sal_Int32 lStartPos = 1;
+ if (nArgCount >= 4)
+ {
+ if (rPar.Get(4)->GetType() != SbxEMPTY)
+ {
+ lStartPos = rPar.Get(4)->GetLong();
+ }
+ if (lStartPos < 1)
+ {
+ return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
+ }
+ }
+ --lStartPos; // Make it 0-based
+
+ sal_Int32 lCount = -1;
+ if (nArgCount >= 5)
+ {
+ if (rPar.Get(5)->GetType() != SbxEMPTY)
+ {
+ lCount = rPar.Get(5)->GetLong();
+ }
+ if (lCount < -1)
+ {
+ return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
+ }
+ }
+
+ bool bCaseInsensitive;
+ if (nArgCount == 6)
+ {
+ bCaseInsensitive = rPar.Get(6)->GetInteger();
+ }
+ else
+ {
+ SbiInstance* pInst = GetSbData()->pInst;
+ if (pInst && pInst->IsCompatibility())
+ {
+ SbiRuntime* pRT = pInst->pRun;
+ bCaseInsensitive = pRT && pRT->IsImageFlag(SbiImageFlags::COMPARETEXT);
+ }
+ else
+ {
+ bCaseInsensitive = true;
+ }
+ }
+
+ const OUString aExpStr = rPar.Get(1)->GetOUString();
+ OUString aFindStr = rPar.Get(2)->GetOUString();
+ const OUString aReplaceStr = rPar.Get(3)->GetOUString();
+
+ OUString aSrcStr(aExpStr);
+ sal_Int32 nPrevPos = std::min(lStartPos, aSrcStr.getLength());
+ css::uno::Sequence<sal_Int32> aOffset;
+ if (bCaseInsensitive)
+ {
+ // tdf#132389: case-insensitive operation for non-ASCII characters
+ // tdf#142487: use css::i18n::Transliteration to correctly handle ß -> ss expansion
+ // tdf#132388: We can't use utl::TextSearch (css::i18n::XTextSearch), because each call to
+ // css::i18n::XTextSearch::SearchForward transliterates input string, making
+ // performance of repeated calls unacceptable
+ auto xTrans = css::i18n::Transliteration::create(comphelper::getProcessComponentContext());
+ xTrans->loadModule(css::i18n::TransliterationModules_IGNORE_CASE, {});
+ aFindStr = xTrans->transliterate(aFindStr, 0, aFindStr.getLength(), aOffset);
+ aSrcStr = xTrans->transliterate(aSrcStr, nPrevPos, aSrcStr.getLength() - nPrevPos, aOffset);
+ nPrevPos = std::distance(aOffset.begin(),
+ std::lower_bound(aOffset.begin(), aOffset.end(), nPrevPos));
+ }
+
+ auto getExpStrPos = [aOffset, nExpLen = aExpStr.getLength()](sal_Int32 nSrcStrPos) -> sal_Int32
+ {
+ assert(!aOffset.hasElements() || aOffset.getLength() >= nSrcStrPos);
+ if (!aOffset.hasElements())
+ return nSrcStrPos;
+ return aOffset.getLength() > nSrcStrPos ? aOffset[nSrcStrPos] : nExpLen;
+ };
+
+ // Note: the result starts from lStartPos, removing everything to the left. See i#94895.
+ OUStringBuffer sResult(aSrcStr.getLength() - nPrevPos);
+ sal_Int32 nCounts = 0;
+ while (lCount == -1 || lCount > nCounts)
+ {
+ sal_Int32 nPos = aSrcStr.indexOf(aFindStr, nPrevPos);
+ if (nPos < 0)
+ break;
+
+ lStartPos = getExpStrPos(nPrevPos);
+ sResult.append(aExpStr.getStr() + lStartPos, getExpStrPos(nPos) - lStartPos);
+ sResult.append(aReplaceStr);
+ nPrevPos = nPos + aFindStr.getLength();
+ nCounts++;
+ }
+ lStartPos = getExpStrPos(nPrevPos);
+ sResult.append(aExpStr.getStr() + lStartPos, aExpStr.getLength() - lStartPos);
+ rPar.Get(0)->PutString(sResult.makeStringAndClear());
+}
+
+void SbRtl_Right(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 3)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ const OUString& rStr = rPar.Get(1)->GetOUString();
+ int nResultLen = rPar.Get(2)->GetLong();
+ if( nResultLen < 0 )
+ {
+ nResultLen = 0;
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ int nStrLen = rStr.getLength();
+ if ( nResultLen > nStrLen )
+ {
+ nResultLen = nStrLen;
+ }
+ OUString aResultStr = rStr.copy( nStrLen - nResultLen );
+ rPar.Get(0)->PutString(aResultStr);
+ }
+}
+
+void SbRtl_RTL(StarBASIC * pBasic, SbxArray & rPar, bool)
+{
+ rPar.Get(0)->PutObject(pBasic->getRTL().get());
+}
+
+void SbRtl_RTrim(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ OUString aStr(comphelper::string::stripEnd(rPar.Get(1)->GetOUString(), ' '));
+ rPar.Get(0)->PutString(aStr);
+}
+
+void SbRtl_Sgn(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ double aDouble = rPar.Get(1)->GetDouble();
+ sal_Int16 nResult = 0;
+ if ( aDouble > 0 )
+ {
+ nResult = 1;
+ }
+ else if ( aDouble < 0 )
+ {
+ nResult = -1;
+ }
+ rPar.Get(0)->PutInteger(nResult);
+ }
+}
+
+void SbRtl_Space(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ const sal_Int32 nCount = rPar.Get(1)->GetLong();
+ OUStringBuffer aBuf(nCount);
+ string::padToLength(aBuf, nCount, ' ');
+ rPar.Get(0)->PutString(aBuf.makeStringAndClear());
+ }
+}
+
+void SbRtl_Sqr(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ double aDouble = rPar.Get(1)->GetDouble();
+ if ( aDouble >= 0 )
+ {
+ rPar.Get(0)->PutDouble(sqrt(aDouble));
+ }
+ else
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ }
+}
+
+void SbRtl_Str(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ OUString aStr;
+ OUString aStrNew("");
+ SbxVariableRef pArg = rPar.Get(1);
+ pArg->Format( aStr );
+
+ // Numbers start with a space
+ if( pArg->IsNumericRTL() )
+ {
+ // replace commas by points so that it's symmetric to Val!
+ aStr = aStr.replaceFirst( ",", "." );
+
+ SbiInstance* pInst = GetSbData()->pInst;
+ bool bCompatibility = ( pInst && pInst->IsCompatibility() );
+ if( bCompatibility )
+ {
+ sal_Int32 nLen = aStr.getLength();
+
+ const sal_Unicode* pBuf = aStr.getStr();
+
+ bool bNeg = ( pBuf[0] == '-' );
+ sal_Int32 iZeroSearch = 0;
+ if( bNeg )
+ {
+ aStrNew += "-";
+ iZeroSearch++;
+ }
+ else
+ {
+ if( pBuf[0] != ' ' )
+ {
+ aStrNew += " ";
+ }
+ }
+ sal_Int32 iNext = iZeroSearch + 1;
+ if( pBuf[iZeroSearch] == '0' && nLen > iNext && pBuf[iNext] == '.' )
+ {
+ iZeroSearch += 1;
+ }
+ aStrNew += aStr.subView(iZeroSearch);
+ }
+ else
+ {
+ aStrNew = " " + aStr;
+ }
+ }
+ else
+ {
+ aStrNew = aStr;
+ }
+ rPar.Get(0)->PutString(aStrNew);
+ }
+}
+
+void SbRtl_StrComp(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 3)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ rPar.Get(0)->PutEmpty();
+ return;
+ }
+ const OUString& rStr1 = rPar.Get(1)->GetOUString();
+ const OUString& rStr2 = rPar.Get(2)->GetOUString();
+
+ SbiInstance* pInst = GetSbData()->pInst;
+ bool bTextCompare;
+ bool bCompatibility = ( pInst && pInst->IsCompatibility() );
+ if( bCompatibility )
+ {
+ SbiRuntime* pRT = pInst->pRun;
+ bTextCompare = pRT && pRT->IsImageFlag( SbiImageFlags::COMPARETEXT );
+ }
+ else
+ {
+ bTextCompare = true;
+ }
+ if (rPar.Count() == 4)
+ bTextCompare = rPar.Get(3)->GetInteger();
+
+ if( !bCompatibility )
+ {
+ bTextCompare = !bTextCompare;
+ }
+ sal_Int32 nRetValue = 0;
+ if( bTextCompare )
+ {
+ ::utl::TransliterationWrapper* pTransliterationWrapper = GetSbData()->pTransliterationWrapper.get();
+ if( !pTransliterationWrapper )
+ {
+ uno::Reference< uno::XComponentContext > xContext = getProcessComponentContext();
+ GetSbData()->pTransliterationWrapper.reset(
+ new ::utl::TransliterationWrapper( xContext,
+ TransliterationFlags::IGNORE_CASE |
+ TransliterationFlags::IGNORE_KANA |
+ TransliterationFlags::IGNORE_WIDTH ) );
+ pTransliterationWrapper = GetSbData()->pTransliterationWrapper.get();
+ }
+
+ LanguageType eLangType = Application::GetSettings().GetLanguageTag().getLanguageType();
+ pTransliterationWrapper->loadModuleIfNeeded( eLangType );
+ nRetValue = pTransliterationWrapper->compareString( rStr1, rStr2 );
+ }
+ else
+ {
+ sal_Int32 aResult;
+ aResult = rStr1.compareTo( rStr2 );
+ if ( aResult < 0 )
+ {
+ nRetValue = -1;
+ }
+ else if ( aResult > 0)
+ {
+ nRetValue = 1;
+ }
+ }
+ rPar.Get(0)->PutInteger(sal::static_int_cast<sal_Int16>(nRetValue));
+}
+
+void SbRtl_String(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ sal_Unicode aFiller;
+ sal_Int32 lCount = rPar.Get(1)->GetLong();
+ if( lCount < 0 || lCount > 0xffff )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ if (rPar.Get(2)->GetType() == SbxINTEGER)
+ {
+ aFiller = static_cast<sal_Unicode>(rPar.Get(2)->GetInteger());
+ }
+ else
+ {
+ const OUString& rStr = rPar.Get(2)->GetOUString();
+ aFiller = rStr[0];
+ }
+ OUStringBuffer aBuf(lCount);
+ string::padToLength(aBuf, lCount, aFiller);
+ rPar.Get(0)->PutString(aBuf.makeStringAndClear());
+ }
+}
+
+void SbRtl_Tab(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ else
+ {
+ const sal_Int32 nCount = std::max(rPar.Get(1)->GetLong(), sal_Int32(0));
+ OUStringBuffer aStr(nCount);
+ comphelper::string::padToLength(aStr, nCount, '\t');
+ rPar.Get(0)->PutString(aStr.makeStringAndClear());
+ }
+}
+
+void SbRtl_Tan(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ SbxVariableRef pArg = rPar.Get(1);
+ rPar.Get(0)->PutDouble(tan(pArg->GetDouble()));
+ }
+}
+
+void SbRtl_UCase(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ const CharClass& rCharClass = GetCharClass();
+ OUString aStr(rPar.Get(1)->GetOUString());
+ aStr = rCharClass.uppercase( aStr );
+ rPar.Get(0)->PutString(aStr);
+ }
+}
+
+
+void SbRtl_Val(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ double nResult = 0.0;
+ char* pEndPtr;
+
+ OUString aStr(rPar.Get(1)->GetOUString());
+
+ FilterWhiteSpace( aStr );
+ if ( aStr.getLength() > 1 && aStr[0] == '&' )
+ {
+ int nRadix = 10;
+ char aChar = static_cast<char>(aStr[1]);
+ if ( aChar == 'h' || aChar == 'H' )
+ {
+ nRadix = 16;
+ }
+ else if ( aChar == 'o' || aChar == 'O' )
+ {
+ nRadix = 8;
+ }
+ if ( nRadix != 10 )
+ {
+ OString aByteStr(OUStringToOString(aStr, osl_getThreadTextEncoding()));
+ sal_Int16 nlResult = static_cast<sal_Int16>(strtol( aByteStr.getStr()+2, &pEndPtr, nRadix));
+ nResult = static_cast<double>(nlResult);
+ }
+ }
+ else
+ {
+ rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
+ sal_Int32 nParseEnd = 0;
+ nResult = ::rtl::math::stringToDouble( aStr, '.', ',', &eStatus, &nParseEnd );
+ if ( eStatus != rtl_math_ConversionStatus_Ok )
+ StarBASIC::Error( ERRCODE_BASIC_MATH_OVERFLOW );
+ /* TODO: we should check whether all characters were parsed here,
+ * but earlier code silently ignored trailing nonsense such as "1x"
+ * resulting in 1 with the side effect that any alpha-only-string
+ * like "x" resulted in 0. Not changing that now (2013-03-22) as
+ * user macros may rely on it. */
+#if 0
+ else if ( nParseEnd != aStr.getLength() )
+ StarBASIC::Error( ERRCODE_BASIC_CONVERSION );
+#endif
+ }
+
+ rPar.Get(0)->PutDouble(nResult);
+ }
+}
+
+
+// Helper functions for date conversion
+sal_Int16 implGetDateDay( double aDate )
+{
+ aDate = floor( aDate );
+ Date aRefDate(1899'12'30);
+ aRefDate.AddDays( aDate );
+
+ sal_Int16 nRet = static_cast<sal_Int16>( aRefDate.GetDay() );
+ return nRet;
+}
+
+sal_Int16 implGetDateMonth( double aDate )
+{
+ Date aRefDate(1899'12'30);
+ sal_Int32 nDays = static_cast<sal_Int32>(aDate);
+ aRefDate.AddDays( nDays );
+ sal_Int16 nRet = static_cast<sal_Int16>( aRefDate.GetMonth() );
+ return nRet;
+}
+
+css::util::Date SbxDateToUNODate( const SbxValue* const pVal )
+{
+ double aDate = pVal->GetDate();
+
+ css::util::Date aUnoDate;
+ aUnoDate.Day = implGetDateDay ( aDate );
+ aUnoDate.Month = implGetDateMonth( aDate );
+ aUnoDate.Year = implGetDateYear ( aDate );
+
+ return aUnoDate;
+}
+
+void SbxDateFromUNODate( SbxValue *pVal, const css::util::Date& aUnoDate)
+{
+ double dDate;
+ if( implDateSerial( aUnoDate.Year, aUnoDate.Month, aUnoDate.Day, false, SbDateCorrection::None, dDate ) )
+ {
+ pVal->PutDate( dDate );
+ }
+}
+
+// Function to convert date to UNO date (com.sun.star.util.Date)
+void SbRtl_CDateToUnoDate(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() != 2)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ unoToSbxValue(rPar.Get(0), Any(SbxDateToUNODate(rPar.Get(1))));
+}
+
+// Function to convert date from UNO date (com.sun.star.util.Date)
+void SbRtl_CDateFromUnoDate(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() != 2 || rPar.Get(1)->GetType() != SbxOBJECT)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ Any aAny(sbxToUnoValue(rPar.Get(1), cppu::UnoType<css::util::Date>::get()));
+ css::util::Date aUnoDate;
+ if(aAny >>= aUnoDate)
+ SbxDateFromUNODate(rPar.Get(0), aUnoDate);
+ else
+ SbxBase::SetError( ERRCODE_BASIC_CONVERSION );
+}
+
+css::util::Time SbxDateToUNOTime( const SbxValue* const pVal )
+{
+ double aDate = pVal->GetDate();
+
+ css::util::Time aUnoTime;
+ aUnoTime.Hours = implGetHour ( aDate );
+ aUnoTime.Minutes = implGetMinute ( aDate );
+ aUnoTime.Seconds = implGetSecond ( aDate );
+ aUnoTime.NanoSeconds = 0;
+
+ return aUnoTime;
+}
+
+void SbxDateFromUNOTime( SbxValue *pVal, const css::util::Time& aUnoTime)
+{
+ pVal->PutDate( implTimeSerial(aUnoTime.Hours, aUnoTime.Minutes, aUnoTime.Seconds) );
+}
+
+// Function to convert date to UNO time (com.sun.star.util.Time)
+void SbRtl_CDateToUnoTime(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() != 2)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ unoToSbxValue(rPar.Get(0), Any(SbxDateToUNOTime(rPar.Get(1))));
+}
+
+// Function to convert date from UNO time (com.sun.star.util.Time)
+void SbRtl_CDateFromUnoTime(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() != 2 || rPar.Get(1)->GetType() != SbxOBJECT)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ Any aAny(sbxToUnoValue(rPar.Get(1), cppu::UnoType<css::util::Time>::get()));
+ css::util::Time aUnoTime;
+ if(aAny >>= aUnoTime)
+ SbxDateFromUNOTime(rPar.Get(0), aUnoTime);
+ else
+ SbxBase::SetError( ERRCODE_BASIC_CONVERSION );
+}
+
+css::util::DateTime SbxDateToUNODateTime( const SbxValue* const pVal )
+{
+ double aDate = pVal->GetDate();
+
+ css::util::DateTime aUnoDT;
+ aUnoDT.Day = implGetDateDay ( aDate );
+ aUnoDT.Month = implGetDateMonth( aDate );
+ aUnoDT.Year = implGetDateYear ( aDate );
+ aUnoDT.Hours = implGetHour ( aDate );
+ aUnoDT.Minutes = implGetMinute ( aDate );
+ aUnoDT.Seconds = implGetSecond ( aDate );
+ aUnoDT.NanoSeconds = 0;
+
+ return aUnoDT;
+}
+
+void SbxDateFromUNODateTime( SbxValue *pVal, const css::util::DateTime& aUnoDT)
+{
+ double dDate(0.0);
+ if( implDateTimeSerial( aUnoDT.Year, aUnoDT.Month, aUnoDT.Day,
+ aUnoDT.Hours, aUnoDT.Minutes, aUnoDT.Seconds,
+ dDate ) )
+ {
+ pVal->PutDate( dDate );
+ }
+}
+
+// Function to convert date to UNO date (com.sun.star.util.Date)
+void SbRtl_CDateToUnoDateTime(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() != 2)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ unoToSbxValue(rPar.Get(0), Any(SbxDateToUNODateTime(rPar.Get(1))));
+}
+
+// Function to convert date from UNO date (com.sun.star.util.Date)
+void SbRtl_CDateFromUnoDateTime(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() != 2 || rPar.Get(1)->GetType() != SbxOBJECT)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ Any aAny(sbxToUnoValue(rPar.Get(1), cppu::UnoType<css::util::DateTime>::get()));
+ css::util::DateTime aUnoDT;
+ if(aAny >>= aUnoDT)
+ SbxDateFromUNODateTime(rPar.Get(0), aUnoDT);
+ else
+ SbxBase::SetError( ERRCODE_BASIC_CONVERSION );
+}
+
+// Function to convert date to ISO 8601 date format YYYYMMDD
+void SbRtl_CDateToIso(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() == 2)
+ {
+ double aDate = rPar.Get(1)->GetDate();
+
+ // Date may actually even be -YYYYYMMDD
+ char Buffer[11];
+ sal_Int16 nYear = implGetDateYear( aDate );
+ snprintf( Buffer, sizeof( Buffer ), (nYear < 0 ? "%05d%02d%02d" : "%04d%02d%02d"),
+ static_cast<int>(nYear),
+ static_cast<int>(implGetDateMonth( aDate )),
+ static_cast<int>(implGetDateDay( aDate )) );
+ OUString aRetStr = OUString::createFromAscii( Buffer );
+ rPar.Get(0)->PutString(aRetStr);
+ }
+ else
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+}
+
+// Function to convert date from ISO 8601 date format YYYYMMDD or YYYY-MM-DD
+// And even YYMMDD for compatibility, sigh...
+void SbRtl_CDateFromIso(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() == 2)
+ {
+ do
+ {
+ OUString aStr = rPar.Get(1)->GetOUString();
+ if (aStr.isEmpty())
+ break;
+
+ // Valid formats are
+ // YYYYMMDD -YYYMMDD YYYYYMMDD -YYYYYMMDD YYMMDD
+ // YYYY-MM-DD -YYYY-MM-DD YYYYY-MM-DD -YYYYY-MM-DD
+
+ sal_Int32 nSign = 1;
+ if (aStr[0] == '-')
+ {
+ nSign = -1;
+ aStr = aStr.copy(1);
+ }
+ const sal_Int32 nLen = aStr.getLength();
+
+ // Signed YYMMDD two digit year is invalid.
+ if (nLen == 6 && nSign == -1)
+ break;
+
+ // Now valid
+ // YYYYMMDD YYYYYMMDD YYMMDD
+ // YYYY-MM-DD YYYYY-MM-DD
+ if (nLen != 6 && (nLen < 8 || 11 < nLen))
+ break;
+
+ bool bUseTwoDigitYear = false;
+ std::u16string_view aYearStr, aMonthStr, aDayStr;
+ if (nLen == 6 || nLen == 8 || nLen == 9)
+ {
+ // ((Y)YY)YYMMDD
+ if (!comphelper::string::isdigitAsciiString(aStr))
+ break;
+
+ const sal_Int32 nMonthPos = (nLen == 8 ? 4 : (nLen == 6 ? 2 : 5));
+ if (nMonthPos == 2)
+ bUseTwoDigitYear = true;
+ aYearStr = aStr.subView( 0, nMonthPos );
+ aMonthStr = aStr.subView( nMonthPos, 2 );
+ aDayStr = aStr.subView( nMonthPos + 2, 2 );
+ }
+ else
+ {
+ // (Y)YYYY-MM-DD
+ const sal_Int32 nMonthSep = (nLen == 11 ? 5 : 4);
+ if (aStr.indexOf('-') != nMonthSep)
+ break;
+ if (aStr.indexOf('-', nMonthSep + 1) != nMonthSep + 3)
+ break;
+
+ aYearStr = aStr.subView( 0, nMonthSep );
+ aMonthStr = aStr.subView( nMonthSep + 1, 2 );
+ aDayStr = aStr.subView( nMonthSep + 4, 2 );
+ if ( !comphelper::string::isdigitAsciiString(aYearStr) ||
+ !comphelper::string::isdigitAsciiString(aMonthStr) ||
+ !comphelper::string::isdigitAsciiString(aDayStr))
+ break;
+ }
+
+ double dDate;
+ if (!implDateSerial( static_cast<sal_Int16>(nSign * o3tl::toInt32(aYearStr)),
+ static_cast<sal_Int16>(o3tl::toInt32(aMonthStr)), static_cast<sal_Int16>(o3tl::toInt32(aDayStr)),
+ bUseTwoDigitYear, SbDateCorrection::None, dDate ))
+ break;
+
+ rPar.Get(0)->PutDate(dDate);
+
+ return;
+ }
+ while (false);
+
+ SbxBase::SetError( ERRCODE_BASIC_BAD_PARAMETER );
+ }
+ else
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+}
+
+void SbRtl_DateSerial(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 4)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ sal_Int16 nYear = rPar.Get(1)->GetInteger();
+ sal_Int16 nMonth = rPar.Get(2)->GetInteger();
+ sal_Int16 nDay = rPar.Get(3)->GetInteger();
+
+ double dDate;
+ if( implDateSerial( nYear, nMonth, nDay, true, SbDateCorrection::RollOver, dDate ) )
+ {
+ rPar.Get(0)->PutDate(dDate);
+ }
+}
+
+void SbRtl_TimeSerial(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 4)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ sal_Int16 nHour = rPar.Get(1)->GetInteger();
+ if ( nHour == 24 )
+ {
+ nHour = 0; // because of UNO DateTimes, which go till 24 o'clock
+ }
+ sal_Int16 nMinute = rPar.Get(2)->GetInteger();
+ sal_Int16 nSecond = rPar.Get(3)->GetInteger();
+ if ((nHour < 0 || nHour > 23) ||
+ (nMinute < 0 || nMinute > 59 ) ||
+ (nSecond < 0 || nSecond > 59 ))
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ rPar.Get(0)->PutDate(implTimeSerial(nHour, nMinute, nSecond)); // JSM
+}
+
+void SbRtl_DateValue(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ // #39629 check GetSbData()->pInst, can be called from the URL line
+ std::shared_ptr<SvNumberFormatter> pFormatter;
+ if( GetSbData()->pInst )
+ {
+ pFormatter = GetSbData()->pInst->GetNumberFormatter();
+ }
+ else
+ {
+ sal_uInt32 n; // Dummy
+ pFormatter = SbiInstance::PrepareNumberFormatter( n, n, n );
+ }
+
+ LanguageType eLangType = Application::GetSettings().GetLanguageTag().getLanguageType();
+ sal_uInt32 nIndex = pFormatter->GetStandardIndex( eLangType);
+ double fResult;
+ OUString aStr(rPar.Get(1)->GetOUString());
+ bool bSuccess = pFormatter->IsNumberFormat( aStr, nIndex, fResult );
+ SvNumFormatType nType = pFormatter->GetType( nIndex );
+
+ // DateValue("February 12, 1969") raises error if the system locale is not en_US
+ // It seems that both locale number formatter and English number
+ // formatter are supported in Visual Basic.
+ if( !bSuccess && ( eLangType != LANGUAGE_ENGLISH_US ) )
+ {
+ // Try using LANGUAGE_ENGLISH_US to get the date value.
+ nIndex = pFormatter->GetStandardIndex( LANGUAGE_ENGLISH_US);
+ bSuccess = pFormatter->IsNumberFormat( aStr, nIndex, fResult );
+ nType = pFormatter->GetType( nIndex );
+ }
+
+ if(bSuccess && (nType==SvNumFormatType::DATE || nType==SvNumFormatType::DATETIME))
+ {
+ if ( nType == SvNumFormatType::DATETIME )
+ {
+ // cut time
+ if ( fResult > 0.0 )
+ {
+ fResult = floor( fResult );
+ }
+ else
+ {
+ fResult = ceil( fResult );
+ }
+ }
+ rPar.Get(0)->PutDate(fResult);
+ }
+ else
+ {
+ StarBASIC::Error( ERRCODE_BASIC_CONVERSION );
+ }
+ }
+}
+
+void SbRtl_TimeValue(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ std::shared_ptr<SvNumberFormatter> pFormatter;
+ if( GetSbData()->pInst )
+ pFormatter = GetSbData()->pInst->GetNumberFormatter();
+ else
+ {
+ sal_uInt32 n;
+ pFormatter = SbiInstance::PrepareNumberFormatter( n, n, n );
+ }
+
+ sal_uInt32 nIndex = 0;
+ double fResult;
+ bool bSuccess = pFormatter->IsNumberFormat(rPar.Get(1)->GetOUString(),
+ nIndex, fResult );
+ SvNumFormatType nType = pFormatter->GetType(nIndex);
+ if(bSuccess && (nType==SvNumFormatType::TIME||nType==SvNumFormatType::DATETIME))
+ {
+ if ( nType == SvNumFormatType::DATETIME )
+ {
+ // cut days
+ fResult = fmod( fResult, 1 );
+ }
+ rPar.Get(0)->PutDate(fResult);
+ }
+ else
+ {
+ StarBASIC::Error( ERRCODE_BASIC_CONVERSION );
+ }
+ }
+}
+
+void SbRtl_Day(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ SbxVariableRef pArg = rPar.Get(1);
+ double aDate = pArg->GetDate();
+
+ sal_Int16 nDay = implGetDateDay( aDate );
+ rPar.Get(0)->PutInteger(nDay);
+ }
+}
+
+void SbRtl_Year(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ sal_Int16 nYear = implGetDateYear(rPar.Get(1)->GetDate());
+ rPar.Get(0)->PutInteger(nYear);
+ }
+}
+
+sal_Int16 implGetHour( double dDate )
+{
+ double nFrac = dDate - floor( dDate );
+ nFrac *= 86400.0;
+ sal_Int32 nSeconds = static_cast<sal_Int32>(nFrac + 0.5);
+ sal_Int16 nHour = static_cast<sal_Int16>(nSeconds / 3600);
+ return nHour;
+}
+
+void SbRtl_Hour(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ double nArg = rPar.Get(1)->GetDate();
+ sal_Int16 nHour = implGetHour( nArg );
+ rPar.Get(0)->PutInteger(nHour);
+ }
+}
+
+void SbRtl_Minute(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ double nArg = rPar.Get(1)->GetDate();
+ sal_Int16 nMin = implGetMinute( nArg );
+ rPar.Get(0)->PutInteger(nMin);
+ }
+}
+
+void SbRtl_Month(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ sal_Int16 nMonth = implGetDateMonth(rPar.Get(1)->GetDate());
+ rPar.Get(0)->PutInteger(nMonth);
+ }
+}
+
+sal_Int16 implGetSecond( double dDate )
+{
+ double nFrac = dDate - floor( dDate );
+ nFrac *= 86400.0;
+ sal_Int32 nSeconds = static_cast<sal_Int32>(nFrac + 0.5);
+ sal_Int16 nTemp = static_cast<sal_Int16>(nSeconds / 3600);
+ nSeconds -= nTemp * 3600;
+ nTemp = static_cast<sal_Int16>(nSeconds / 60);
+ nSeconds -= nTemp * 60;
+
+ sal_Int16 nRet = static_cast<sal_Int16>(nSeconds);
+ return nRet;
+}
+
+void SbRtl_Second(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ double nArg = rPar.Get(1)->GetDate();
+ sal_Int16 nSecond = implGetSecond( nArg );
+ rPar.Get(0)->PutInteger(nSecond);
+ }
+}
+
+double Now_Impl()
+{
+ DateTime aDateTime( DateTime::SYSTEM );
+ double aSerial = static_cast<double>(GetDayDiff( aDateTime ));
+ tools::Long nSeconds = aDateTime.GetHour();
+ nSeconds *= 3600;
+ nSeconds += aDateTime.GetMin() * 60;
+ nSeconds += aDateTime.GetSec();
+ double nDays = static_cast<double>(nSeconds) / (24.0*3600.0);
+ aSerial += nDays;
+ return aSerial;
+}
+
+// Date Now()
+
+void SbRtl_Now(StarBASIC*, SbxArray& rPar, bool) { rPar.Get(0)->PutDate(Now_Impl()); }
+
+// Date Time()
+
+void SbRtl_Time(StarBASIC *, SbxArray & rPar, bool bWrite)
+{
+ if ( !bWrite )
+ {
+ tools::Time aTime( tools::Time::SYSTEM );
+ SbxVariable* pMeth = rPar.Get(0);
+ OUString aRes;
+ if( pMeth->IsFixed() )
+ {
+ // Time$: hh:mm:ss
+ char buf[ 20 ];
+ snprintf( buf, sizeof(buf), "%02d:%02d:%02d",
+ aTime.GetHour(), aTime.GetMin(), aTime.GetSec() );
+ aRes = OUString::createFromAscii( buf );
+ }
+ else
+ {
+ // Time: system dependent
+ tools::Long nSeconds=aTime.GetHour();
+ nSeconds *= 3600;
+ nSeconds += aTime.GetMin() * 60;
+ nSeconds += aTime.GetSec();
+ double nDays = static_cast<double>(nSeconds) * ( 1.0 / (24.0*3600.0) );
+ const Color* pCol;
+
+ std::shared_ptr<SvNumberFormatter> pFormatter;
+ sal_uInt32 nIndex;
+ if( GetSbData()->pInst )
+ {
+ pFormatter = GetSbData()->pInst->GetNumberFormatter();
+ nIndex = GetSbData()->pInst->GetStdTimeIdx();
+ }
+ else
+ {
+ sal_uInt32 n; // Dummy
+ pFormatter = SbiInstance::PrepareNumberFormatter( n, nIndex, n );
+ }
+
+ pFormatter->GetOutputString( nDays, nIndex, aRes, &pCol );
+ }
+ pMeth->PutString( aRes );
+ }
+ else
+ {
+ StarBASIC::Error( ERRCODE_BASIC_NOT_IMPLEMENTED );
+ }
+}
+
+void SbRtl_Timer(StarBASIC *, SbxArray & rPar, bool)
+{
+ tools::Time aTime( tools::Time::SYSTEM );
+ tools::Long nSeconds = aTime.GetHour();
+ nSeconds *= 3600;
+ nSeconds += aTime.GetMin() * 60;
+ nSeconds += aTime.GetSec();
+ rPar.Get(0)->PutDate(static_cast<double>(nSeconds));
+}
+
+
+void SbRtl_Date(StarBASIC *, SbxArray & rPar, bool bWrite)
+{
+ if ( !bWrite )
+ {
+ Date aToday( Date::SYSTEM );
+ double nDays = static_cast<double>(GetDayDiff( aToday ));
+ SbxVariable* pMeth = rPar.Get(0);
+ if( pMeth->IsString() )
+ {
+ OUString aRes;
+ const Color* pCol;
+
+ std::shared_ptr<SvNumberFormatter> pFormatter;
+ sal_uInt32 nIndex;
+ if( GetSbData()->pInst )
+ {
+ pFormatter = GetSbData()->pInst->GetNumberFormatter();
+ nIndex = GetSbData()->pInst->GetStdDateIdx();
+ }
+ else
+ {
+ sal_uInt32 n;
+ pFormatter = SbiInstance::PrepareNumberFormatter( nIndex, n, n );
+ }
+
+ pFormatter->GetOutputString( nDays, nIndex, aRes, &pCol );
+ pMeth->PutString( aRes );
+ }
+ else
+ {
+ pMeth->PutDate( nDays );
+ }
+ }
+ else
+ {
+ StarBASIC::Error( ERRCODE_BASIC_NOT_IMPLEMENTED );
+ }
+}
+
+void SbRtl_IsArray(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ rPar.Get(0)->PutBool((rPar.Get(1)->GetType() & SbxARRAY) != 0);
+ }
+}
+
+void SbRtl_IsObject(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ SbxVariable* pVar = rPar.Get(1);
+ bool bObject = pVar->IsObject();
+ SbxBase* pObj = (bObject ? pVar->GetObject() : nullptr);
+
+ if( auto pUnoClass = dynamic_cast<SbUnoClass*>( pObj) )
+ {
+ bObject = pUnoClass->getUnoClass().is();
+ }
+ rPar.Get(0)->PutBool(bObject);
+ }
+}
+
+void SbRtl_IsDate(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ // #46134 only string is converted, all other types result in sal_False
+ SbxVariableRef xArg = rPar.Get(1);
+ SbxDataType eType = xArg->GetType();
+ bool bDate = false;
+
+ if( eType == SbxDATE )
+ {
+ bDate = true;
+ }
+ else if( eType == SbxSTRING )
+ {
+ ErrCode nPrevError = SbxBase::GetError();
+ SbxBase::ResetError();
+
+ // force conversion of the parameter to SbxDATE
+ xArg->SbxValue::GetDate();
+
+ bDate = !SbxBase::IsError();
+
+ SbxBase::ResetError();
+ SbxBase::SetError( nPrevError );
+ }
+ rPar.Get(0)->PutBool(bDate);
+ }
+}
+
+void SbRtl_IsEmpty(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ SbxVariable* pVar = nullptr;
+ if( SbiRuntime::isVBAEnabled() )
+ {
+ pVar = getDefaultProp(rPar.Get(1));
+ }
+ if ( pVar )
+ {
+ pVar->Broadcast( SfxHintId::BasicDataWanted );
+ rPar.Get(0)->PutBool(pVar->IsEmpty());
+ }
+ else
+ {
+ rPar.Get(0)->PutBool(rPar.Get(1)->IsEmpty());
+ }
+ }
+}
+
+void SbRtl_IsError(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ SbxVariable* pVar = rPar.Get(1);
+ SbUnoObject* pObj = dynamic_cast<SbUnoObject*>( pVar );
+ if ( !pObj )
+ {
+ if ( SbxBase* pBaseObj = (pVar->IsObject() ? pVar->GetObject() : nullptr) )
+ {
+ pObj = dynamic_cast<SbUnoObject*>( pBaseObj );
+ }
+ }
+ uno::Reference< script::XErrorQuery > xError;
+ if ( pObj )
+ {
+ xError.set( pObj->getUnoAny(), uno::UNO_QUERY );
+ }
+ if ( xError.is() )
+ {
+ rPar.Get(0)->PutBool(xError->hasError());
+ }
+ else
+ {
+ rPar.Get(0)->PutBool(rPar.Get(1)->IsErr());
+ }
+ }
+}
+
+void SbRtl_IsNull(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ // #51475 because of Uno-objects return true
+ // even if the pObj value is NULL
+ SbxVariableRef pArg = rPar.Get(1);
+ bool bNull = rPar.Get(1)->IsNull();
+ if( !bNull && pArg->GetType() == SbxOBJECT )
+ {
+ SbxBase* pObj = pArg->GetObject();
+ if( !pObj )
+ {
+ bNull = true;
+ }
+ }
+ rPar.Get(0)->PutBool(bNull);
+ }
+}
+
+void SbRtl_IsNumeric(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() < 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ rPar.Get(0)->PutBool(rPar.Get(1)->IsNumericRTL());
+ }
+}
+
+
+void SbRtl_IsMissing(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() != 2)
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ // #57915 Missing is reported by an error
+ rPar.Get(0)->PutBool(rPar.Get(1)->IsErr());
+}
+
+// Function looks for wildcards, removes them and always returns the pure path
+static OUString implSetupWildcard(const OUString& rFileParam, SbiRTLData& rRTLData)
+{
+ static const char cDelim1 = '/';
+ static const char cDelim2 = '\\';
+ static const char cWild1 = '*';
+ static const char cWild2 = '?';
+
+ rRTLData.moWildCard.reset();
+ rRTLData.sFullNameToBeChecked.clear();
+
+ OUString aFileParam = rFileParam;
+ sal_Int32 nLastWild = aFileParam.lastIndexOf( cWild1 );
+ if( nLastWild < 0 )
+ {
+ nLastWild = aFileParam.lastIndexOf( cWild2 );
+ }
+ bool bHasWildcards = ( nLastWild >= 0 );
+
+
+ sal_Int32 nLastDelim = aFileParam.lastIndexOf( cDelim1 );
+ if( nLastDelim < 0 )
+ {
+ nLastDelim = aFileParam.lastIndexOf( cDelim2 );
+ }
+ if( bHasWildcards )
+ {
+ // Wildcards in path?
+ if( nLastDelim >= 0 && nLastDelim > nLastWild )
+ {
+ return aFileParam;
+ }
+ }
+ else
+ {
+ OUString aPathStr = getFullPath( aFileParam );
+ if( nLastDelim != aFileParam.getLength() - 1 )
+ {
+ rRTLData.sFullNameToBeChecked = aPathStr;
+ }
+ return aPathStr;
+ }
+
+ OUString aPureFileName;
+ if( nLastDelim < 0 )
+ {
+ aPureFileName = aFileParam;
+ aFileParam.clear();
+ }
+ else
+ {
+ aPureFileName = aFileParam.copy( nLastDelim + 1 );
+ aFileParam = aFileParam.copy( 0, nLastDelim );
+ }
+
+ // Try again to get a valid URL/UNC-path with only the path
+ OUString aPathStr = getFullPath( aFileParam );
+
+ // Is there a pure file name left? Otherwise the path is
+ // invalid anyway because it was not accepted by OSL before
+ if (aPureFileName != "*")
+ {
+ rRTLData.moWildCard.emplace(aPureFileName);
+ }
+ return aPathStr;
+}
+
+static bool implCheckWildcard(std::u16string_view rName, SbiRTLData const& rRTLData)
+{
+ bool bMatch = true;
+
+ if (rRTLData.moWildCard)
+ {
+ bMatch = rRTLData.moWildCard->Matches(rName);
+ }
+ return bMatch;
+}
+
+
+static bool isRootDir( std::u16string_view aDirURLStr )
+{
+ INetURLObject aDirURLObj( aDirURLStr );
+ bool bRoot = false;
+
+ // Check if it's a root directory
+ sal_Int32 nCount = aDirURLObj.getSegmentCount();
+
+ // No segment means Unix root directory "file:///"
+ if( nCount == 0 )
+ {
+ bRoot = true;
+ }
+ // Exactly one segment needs further checking, because it
+ // can be Unix "file:///foo/" -> no root
+ // or Windows "file:///c:/" -> root
+ else if( nCount == 1 )
+ {
+ OUString aSeg1 = aDirURLObj.getName( 0, true,
+ INetURLObject::DecodeMechanism::WithCharset );
+ if( aSeg1[1] == ':' )
+ {
+ bRoot = true;
+ }
+ }
+ // More than one segments can never be root
+ // so bRoot remains false
+
+ return bRoot;
+}
+
+void SbRtl_Dir(StarBASIC *, SbxArray & rPar, bool)
+{
+ OUString aPath;
+
+ const sal_uInt32 nParCount = rPar.Count();
+ if( nParCount > 3 )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ SbiRTLData& rRTLData = GetSbData()->pInst->GetRTLData();
+
+ if( hasUno() )
+ {
+ const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
+ if( xSFI.is() )
+ {
+ if ( nParCount >= 2 )
+ {
+ OUString aFileParam = rPar.Get(1)->GetOUString();
+
+ OUString aFileURLStr = implSetupWildcard(aFileParam, rRTLData);
+ if (!rRTLData.sFullNameToBeChecked.isEmpty())
+ {
+ bool bExists = false;
+ try { bExists = xSFI->exists( aFileURLStr ); }
+ catch(const Exception & ) {}
+
+ OUString aNameOnlyStr;
+ if( bExists )
+ {
+ INetURLObject aFileURL( aFileURLStr );
+ aNameOnlyStr = aFileURL.getName( INetURLObject::LAST_SEGMENT,
+ true, INetURLObject::DecodeMechanism::WithCharset );
+ }
+ rPar.Get(0)->PutString(aNameOnlyStr);
+ return;
+ }
+
+ try
+ {
+ OUString aDirURLStr;
+ bool bFolder = xSFI->isFolder( aFileURLStr );
+
+ if( bFolder )
+ {
+ aDirURLStr = aFileURLStr;
+ }
+ else
+ {
+ rPar.Get(0)->PutString("");
+ }
+
+ SbAttributes nFlags = SbAttributes::NONE;
+ if ( nParCount > 2 )
+ {
+ rRTLData.nDirFlags = nFlags
+ = static_cast<SbAttributes>(rPar.Get(2)->GetInteger());
+ }
+ else
+ {
+ rRTLData.nDirFlags = SbAttributes::NONE;
+ }
+ // Read directory
+ bool bIncludeFolders = bool(nFlags & SbAttributes::DIRECTORY);
+ rRTLData.aDirSeq = xSFI->getFolderContents(aDirURLStr, bIncludeFolders);
+ rRTLData.nCurDirPos = 0;
+
+ // #78651 Add "." and ".." directories for VB compatibility
+ if( bIncludeFolders )
+ {
+ bool bRoot = isRootDir( aDirURLStr );
+
+ // If it's no root directory we flag the need for
+ // the "." and ".." directories by the value -2
+ // for the actual position. Later for -2 will be
+ // returned "." and for -1 ".."
+ if( !bRoot )
+ {
+ rRTLData.nCurDirPos = -2;
+ }
+ }
+ }
+ catch(const Exception & )
+ {
+ }
+ }
+
+
+ if (rRTLData.aDirSeq.hasElements())
+ {
+ bool bFolderFlag = bool(rRTLData.nDirFlags & SbAttributes::DIRECTORY);
+
+ SbiInstance* pInst = GetSbData()->pInst;
+ bool bCompatibility = ( pInst && pInst->IsCompatibility() );
+ for( ;; )
+ {
+ if (rRTLData.nCurDirPos < 0)
+ {
+ if (rRTLData.nCurDirPos == -2)
+ {
+ aPath = ".";
+ }
+ else if (rRTLData.nCurDirPos == -1)
+ {
+ aPath = "..";
+ }
+ rRTLData.nCurDirPos++;
+ }
+ else if (rRTLData.nCurDirPos >= rRTLData.aDirSeq.getLength())
+ {
+ rRTLData.aDirSeq.realloc(0);
+ aPath.clear();
+ break;
+ }
+ else
+ {
+ OUString aFile
+ = rRTLData.aDirSeq.getConstArray()[rRTLData.nCurDirPos++];
+
+ if( bCompatibility )
+ {
+ if( !bFolderFlag )
+ {
+ bool bFolder = xSFI->isFolder( aFile );
+ if( bFolder )
+ {
+ continue;
+ }
+ }
+ }
+ else
+ {
+ // Only directories
+ if( bFolderFlag )
+ {
+ bool bFolder = xSFI->isFolder( aFile );
+ if( !bFolder )
+ {
+ continue;
+ }
+ }
+ }
+
+ INetURLObject aURL( aFile );
+ aPath = aURL.getName( INetURLObject::LAST_SEGMENT, true,
+ INetURLObject::DecodeMechanism::WithCharset );
+ }
+
+ bool bMatch = implCheckWildcard(aPath, rRTLData);
+ if( !bMatch )
+ {
+ continue;
+ }
+ break;
+ }
+ }
+ rPar.Get(0)->PutString(aPath);
+ }
+ }
+ else
+ {
+ // TODO: OSL
+ if ( nParCount >= 2 )
+ {
+ OUString aFileParam = rPar.Get(1)->GetOUString();
+
+ OUString aDirURL = implSetupWildcard(aFileParam, rRTLData);
+
+ SbAttributes nFlags = SbAttributes::NONE;
+ if ( nParCount > 2 )
+ {
+ rRTLData.nDirFlags = nFlags
+ = static_cast<SbAttributes>(rPar.Get(2)->GetInteger());
+ }
+ else
+ {
+ rRTLData.nDirFlags = SbAttributes::NONE;
+ }
+
+ // Read directory
+ bool bIncludeFolders = bool(nFlags & SbAttributes::DIRECTORY);
+ rRTLData.pDir = std::make_unique<Directory>(aDirURL);
+ FileBase::RC nRet = rRTLData.pDir->open();
+ if( nRet != FileBase::E_None )
+ {
+ rRTLData.pDir.reset();
+ rPar.Get(0)->PutString(OUString());
+ return;
+ }
+
+ // #86950 Add "." and ".." directories for VB compatibility
+ rRTLData.nCurDirPos = 0;
+ if( bIncludeFolders )
+ {
+ bool bRoot = isRootDir( aDirURL );
+
+ // If it's no root directory we flag the need for
+ // the "." and ".." directories by the value -2
+ // for the actual position. Later for -2 will be
+ // returned "." and for -1 ".."
+ if( !bRoot )
+ {
+ rRTLData.nCurDirPos = -2;
+ }
+ }
+
+ }
+
+ if (rRTLData.pDir)
+ {
+ bool bFolderFlag = bool(rRTLData.nDirFlags & SbAttributes::DIRECTORY);
+ for( ;; )
+ {
+ if (rRTLData.nCurDirPos < 0)
+ {
+ if (rRTLData.nCurDirPos == -2)
+ {
+ aPath = ".";
+ }
+ else if (rRTLData.nCurDirPos == -1)
+ {
+ aPath = "..";
+ }
+ rRTLData.nCurDirPos++;
+ }
+ else
+ {
+ DirectoryItem aItem;
+ FileBase::RC nRet = rRTLData.pDir->getNextItem(aItem);
+ if( nRet != FileBase::E_None )
+ {
+ rRTLData.pDir.reset();
+ aPath.clear();
+ break;
+ }
+
+ // Handle flags
+ FileStatus aFileStatus( osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName );
+ nRet = aItem.getFileStatus( aFileStatus );
+ if( nRet != FileBase::E_None )
+ {
+ SAL_WARN("basic", "getFileStatus failed");
+ continue;
+ }
+
+ // Only directories?
+ if( bFolderFlag )
+ {
+ FileStatus::Type aType = aFileStatus.getFileType();
+ bool bFolder = isFolder( aType );
+ if( !bFolder )
+ {
+ continue;
+ }
+ }
+
+ aPath = aFileStatus.getFileName();
+ }
+
+ bool bMatch = implCheckWildcard(aPath, rRTLData);
+ if( !bMatch )
+ {
+ continue;
+ }
+ break;
+ }
+ }
+ rPar.Get(0)->PutString(aPath);
+ }
+ }
+}
+
+
+void SbRtl_GetAttr(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() == 2)
+ {
+ sal_Int16 nFlags = 0;
+
+ // In Windows, we want to use Windows API to get the file attributes
+ // for VBA interoperability.
+ #if defined(_WIN32)
+ if( SbiRuntime::isVBAEnabled() )
+ {
+ OUString aPathURL = getFullPath(rPar.Get(1)->GetOUString());
+ OUString aPath;
+ FileBase::getSystemPathFromFileURL( aPathURL, aPath );
+ DWORD nRealFlags = GetFileAttributesW (o3tl::toW(aPath.getStr()));
+ if (nRealFlags != 0xffffffff)
+ {
+ if (nRealFlags == FILE_ATTRIBUTE_NORMAL)
+ {
+ nRealFlags = 0;
+ }
+ nFlags = static_cast<sal_Int16>(nRealFlags);
+ }
+ else
+ {
+ StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
+ }
+ rPar.Get(0)->PutInteger(nFlags);
+
+ return;
+ }
+ #endif
+
+ if( hasUno() )
+ {
+ const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
+ if( xSFI.is() )
+ {
+ try
+ {
+ OUString aPath = getFullPath(rPar.Get(1)->GetOUString());
+ bool bExists = false;
+ try { bExists = xSFI->exists( aPath ); }
+ catch(const Exception & ) {}
+ if( !bExists )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
+ }
+
+ bool bReadOnly = xSFI->isReadOnly( aPath );
+ bool bHidden = xSFI->isHidden( aPath );
+ bool bDirectory = xSFI->isFolder( aPath );
+ if( bReadOnly )
+ {
+ nFlags |= sal_uInt16(SbAttributes::READONLY);
+ }
+ if( bHidden )
+ {
+ nFlags |= sal_uInt16(SbAttributes::HIDDEN);
+ }
+ if( bDirectory )
+ {
+ nFlags |= sal_uInt16(SbAttributes::DIRECTORY);
+ }
+ }
+ catch(const Exception & )
+ {
+ StarBASIC::Error( ERRCODE_IO_GENERAL );
+ }
+ }
+ }
+ else
+ {
+ DirectoryItem aItem;
+ (void)DirectoryItem::get(getFullPath(rPar.Get(1)->GetOUString()), aItem);
+ FileStatus aFileStatus( osl_FileStatus_Mask_Attributes | osl_FileStatus_Mask_Type );
+ (void)aItem.getFileStatus( aFileStatus );
+ sal_uInt64 nAttributes = aFileStatus.getAttributes();
+ bool bReadOnly = (nAttributes & osl_File_Attribute_ReadOnly) != 0;
+
+ FileStatus::Type aType = aFileStatus.getFileType();
+ bool bDirectory = isFolder( aType );
+ if( bReadOnly )
+ {
+ nFlags |= sal_uInt16(SbAttributes::READONLY);
+ }
+ if( bDirectory )
+ {
+ nFlags |= sal_uInt16(SbAttributes::DIRECTORY);
+ }
+ }
+ rPar.Get(0)->PutInteger(nFlags);
+ }
+ else
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+}
+
+
+void SbRtl_FileDateTime(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() != 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ OUString aPath = rPar.Get(1)->GetOUString();
+ tools::Time aTime( tools::Time::EMPTY );
+ Date aDate( Date::EMPTY );
+ if( hasUno() )
+ {
+ const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
+ if( xSFI.is() )
+ {
+ try
+ {
+ util::DateTime aUnoDT = xSFI->getDateTimeModified( aPath );
+ aTime = tools::Time( aUnoDT );
+ aDate = Date( aUnoDT );
+ }
+ catch(const Exception & )
+ {
+ StarBASIC::Error( ERRCODE_IO_GENERAL );
+ }
+ }
+ }
+ else
+ {
+ bool bSuccess = false;
+ do
+ {
+ DirectoryItem aItem;
+ if (DirectoryItem::get( getFullPath( aPath ), aItem ) != FileBase::E_None)
+ break;
+
+ FileStatus aFileStatus( osl_FileStatus_Mask_ModifyTime );
+ if (aItem.getFileStatus( aFileStatus ) != FileBase::E_None)
+ break;
+
+ TimeValue aTimeVal = aFileStatus.getModifyTime();
+ oslDateTime aDT;
+ if (!osl_getDateTimeFromTimeValue( &aTimeVal, &aDT ))
+ // Strictly spoken this is not an i/o error but some other failure.
+ break;
+
+ aTime = tools::Time( aDT.Hours, aDT.Minutes, aDT.Seconds, aDT.NanoSeconds );
+ aDate = Date( aDT.Day, aDT.Month, aDT.Year );
+ bSuccess = true;
+ }
+ while(false);
+
+ if (!bSuccess)
+ StarBASIC::Error( ERRCODE_IO_GENERAL );
+ }
+
+ // An empty date shall not result in a formatted null-date (1899-12-30
+ // or 1900-01-01) or even worse -0001-12-03 or some such due to how
+ // GetDayDiff() treats things. There should be an error set in this
+ // case anyway because of a missing file or other error above, but... so
+ // do not even bother to use the number formatter.
+ OUString aRes;
+ if (aDate.IsEmpty())
+ {
+ aRes = "0000-00-00 00:00:00";
+ }
+ else
+ {
+ double fSerial = static_cast<double>(GetDayDiff( aDate ));
+ tools::Long nSeconds = aTime.GetHour();
+ nSeconds *= 3600;
+ nSeconds += aTime.GetMin() * 60;
+ nSeconds += aTime.GetSec();
+ double nDays = static_cast<double>(nSeconds) / (24.0*3600.0);
+ fSerial += nDays;
+
+ const Color* pCol;
+
+ std::shared_ptr<SvNumberFormatter> pFormatter;
+ sal_uInt32 nIndex;
+ if( GetSbData()->pInst )
+ {
+ pFormatter = GetSbData()->pInst->GetNumberFormatter();
+ nIndex = GetSbData()->pInst->GetStdDateTimeIdx();
+ }
+ else
+ {
+ sal_uInt32 n;
+ pFormatter = SbiInstance::PrepareNumberFormatter( n, n, nIndex );
+ }
+
+ pFormatter->GetOutputString( fSerial, nIndex, aRes, &pCol );
+ }
+ rPar.Get(0)->PutString(aRes);
+ }
+}
+
+
+void SbRtl_EOF(StarBASIC *, SbxArray & rPar, bool)
+{
+ // No changes for UCB
+ if (rPar.Count() != 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ sal_Int16 nChannel = rPar.Get(1)->GetInteger();
+ SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
+ SbiStream* pSbStrm = pIO->GetStream( nChannel );
+ if ( !pSbStrm )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
+ }
+ bool beof;
+ SvStream* pSvStrm = pSbStrm->GetStrm();
+ if ( pSbStrm->IsText() )
+ {
+ char cBla;
+ (*pSvStrm).ReadChar( cBla ); // can we read another character?
+ beof = pSvStrm->eof();
+ if ( !beof )
+ {
+ pSvStrm->SeekRel( -1 );
+ }
+ }
+ else
+ {
+ beof = pSvStrm->eof(); // for binary data!
+ }
+ rPar.Get(0)->PutBool(beof);
+ }
+}
+
+void SbRtl_FileAttr(StarBASIC *, SbxArray & rPar, bool)
+{
+ // No changes for UCB
+ // #57064 Although this function doesn't operate with DirEntry, it is
+ // not touched by the adjustment to virtual URLs, as it only works on
+ // already opened files and the name doesn't matter there.
+
+ if (rPar.Count() != 3)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ sal_Int16 nChannel = rPar.Get(1)->GetInteger();
+ SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
+ SbiStream* pSbStrm = pIO->GetStream( nChannel );
+ if ( !pSbStrm )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
+ }
+ sal_Int16 nRet;
+ if (rPar.Get(2)->GetInteger() == 1)
+ {
+ nRet = static_cast<sal_Int16>(pSbStrm->GetMode());
+ }
+ else
+ {
+ nRet = 0; // System file handle not supported
+ }
+ rPar.Get(0)->PutInteger(nRet);
+ }
+}
+void SbRtl_Loc(StarBASIC *, SbxArray & rPar, bool)
+{
+ // No changes for UCB
+ if (rPar.Count() != 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ sal_Int16 nChannel = rPar.Get(1)->GetInteger();
+ SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
+ SbiStream* pSbStrm = pIO->GetStream( nChannel );
+ if ( !pSbStrm )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
+ }
+ SvStream* pSvStrm = pSbStrm->GetStrm();
+ std::size_t nPos;
+ if( pSbStrm->IsRandom())
+ {
+ short nBlockLen = pSbStrm->GetBlockLen();
+ nPos = nBlockLen ? (pSvStrm->Tell() / nBlockLen) : 0;
+ nPos++; // block positions starting at 1
+ }
+ else if ( pSbStrm->IsText() )
+ {
+ nPos = pSbStrm->GetLine();
+ }
+ else if( pSbStrm->IsBinary() )
+ {
+ nPos = pSvStrm->Tell();
+ }
+ else if ( pSbStrm->IsSeq() )
+ {
+ nPos = ( pSvStrm->Tell()+1 ) / 128;
+ }
+ else
+ {
+ nPos = pSvStrm->Tell();
+ }
+ rPar.Get(0)->PutLong(static_cast<sal_Int32>(nPos));
+ }
+}
+
+void SbRtl_Lof(StarBASIC *, SbxArray & rPar, bool)
+{
+ // No changes for UCB
+ if (rPar.Count() != 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ sal_Int16 nChannel = rPar.Get(1)->GetInteger();
+ SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
+ SbiStream* pSbStrm = pIO->GetStream( nChannel );
+ if ( !pSbStrm )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
+ }
+ SvStream* pSvStrm = pSbStrm->GetStrm();
+ sal_uInt64 const nLen = pSvStrm->TellEnd();
+ rPar.Get(0)->PutLong(static_cast<sal_Int32>(nLen));
+ }
+}
+
+
+void SbRtl_Seek(StarBASIC *, SbxArray & rPar, bool)
+{
+ // No changes for UCB
+ int nArgs = static_cast<int>(rPar.Count());
+ if ( nArgs < 2 || nArgs > 3 )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ sal_Int16 nChannel = rPar.Get(1)->GetInteger();
+ SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
+ SbiStream* pSbStrm = pIO->GetStream( nChannel );
+ if ( !pSbStrm )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
+ }
+ SvStream* pStrm = pSbStrm->GetStrm();
+
+ if ( nArgs == 2 ) // Seek-Function
+ {
+ sal_uInt64 nPos = pStrm->Tell();
+ if( pSbStrm->IsRandom() )
+ {
+ nPos = nPos / pSbStrm->GetBlockLen();
+ }
+ nPos++; // Basic counts from 1
+ rPar.Get(0)->PutLong(static_cast<sal_Int32>(nPos));
+ }
+ else // Seek-Statement
+ {
+ sal_Int32 nPos = rPar.Get(2)->GetLong();
+ if ( nPos < 1 )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ nPos--; // Basic counts from 1, SvStreams count from 0
+ pSbStrm->SetExpandOnWriteTo( 0 );
+ if ( pSbStrm->IsRandom() )
+ {
+ nPos *= pSbStrm->GetBlockLen();
+ }
+ pStrm->Seek( static_cast<sal_uInt64>(nPos) );
+ pSbStrm->SetExpandOnWriteTo( nPos );
+ }
+}
+
+void SbRtl_Format(StarBASIC *, SbxArray & rPar, bool)
+{
+ const sal_uInt32 nArgCount = rPar.Count();
+ if ( nArgCount < 2 || nArgCount > 3 )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ OUString aResult;
+ if( nArgCount == 2 )
+ {
+ rPar.Get(1)->Format(aResult);
+ }
+ else
+ {
+ OUString aFmt(rPar.Get(2)->GetOUString());
+ rPar.Get(1)->Format(aResult, &aFmt);
+ }
+ rPar.Get(0)->PutString(aResult);
+ }
+}
+
+static bool IsMissing(SbxArray& rPar, const sal_uInt32 i)
+{
+ const sal_uInt32 nArgCount = rPar.Count();
+ if (nArgCount <= i)
+ return true;
+
+ SbxVariable* aPar = rPar.Get(i);
+ return (aPar->GetType() == SbxERROR && SbiRuntime::IsMissing(aPar, 1));
+}
+
+static sal_Int16 GetOptionalIntegerParamOrDefault(SbxArray& rPar, const sal_uInt32 i,
+ const sal_Int16 defaultValue)
+{
+ return IsMissing(rPar, i) ? defaultValue : rPar.Get(i)->GetInteger();
+}
+
+static OUString GetOptionalOUStringParamOrDefault(SbxArray& rPar, const sal_uInt32 i,
+ const OUString& defaultValue)
+{
+ return IsMissing(rPar, i) ? defaultValue : rPar.Get(i)->GetOUString();
+}
+
+static void lcl_FormatNumberPercent(SbxArray& rPar, bool isPercent)
+{
+ const sal_uInt32 nArgCount = rPar.Count();
+ if (nArgCount < 2 || nArgCount > 6)
+ {
+ return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
+ }
+
+ // The UI locale never changes -> we can use static value here
+ static const LocaleDataWrapper localeData(Application::GetSettings().GetUILanguageTag());
+ sal_Int16 nNumDigitsAfterDecimal = -1;
+ if (nArgCount > 2 && !rPar.Get(2)->IsEmpty())
+ {
+ nNumDigitsAfterDecimal = rPar.Get(2)->GetInteger();
+ if (nNumDigitsAfterDecimal < -1)
+ {
+ return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
+ }
+ else if (nNumDigitsAfterDecimal > 255)
+ nNumDigitsAfterDecimal %= 256;
+ }
+ if (nNumDigitsAfterDecimal == -1)
+ nNumDigitsAfterDecimal = LocaleDataWrapper::getNumDigits();
+
+ bool bIncludeLeadingDigit = LocaleDataWrapper::isNumLeadingZero();
+ if (nArgCount > 3 && !rPar.Get(3)->IsEmpty())
+ {
+ switch (rPar.Get(3)->GetInteger())
+ {
+ case ooo::vba::VbTriState::vbFalse:
+ bIncludeLeadingDigit = false;
+ break;
+ case ooo::vba::VbTriState::vbTrue:
+ bIncludeLeadingDigit = true;
+ break;
+ case ooo::vba::VbTriState::vbUseDefault:
+ // do nothing;
+ break;
+ default:
+ return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
+ }
+ }
+
+ bool bUseParensForNegativeNumbers = false;
+ if (nArgCount > 4 && !rPar.Get(4)->IsEmpty())
+ {
+ switch (rPar.Get(4)->GetInteger())
+ {
+ case ooo::vba::VbTriState::vbFalse:
+ case ooo::vba::VbTriState::vbUseDefault:
+ // do nothing
+ break;
+ case ooo::vba::VbTriState::vbTrue:
+ bUseParensForNegativeNumbers = true;
+ break;
+ default:
+ return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
+ }
+ }
+
+ bool bGroupDigits = false;
+ if (nArgCount > 5 && !rPar.Get(5)->IsEmpty())
+ {
+ switch (rPar.Get(5)->GetInteger())
+ {
+ case ooo::vba::VbTriState::vbFalse:
+ case ooo::vba::VbTriState::vbUseDefault:
+ // do nothing
+ break;
+ case ooo::vba::VbTriState::vbTrue:
+ bGroupDigits = true;
+ break;
+ default:
+ return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
+ }
+ }
+
+ double fVal = rPar.Get(1)->GetDouble();
+ if (isPercent)
+ fVal *= 100;
+ const bool bNegative = fVal < 0;
+ if (bNegative)
+ fVal = fabs(fVal); // Always work with non-negatives, to easily handle leading zero
+
+ static const sal_Unicode decSep = localeData.getNumDecimalSep().toChar();
+ OUStringBuffer aResult;
+ rtl::math::doubleToUStringBuffer(aResult,
+ fVal, rtl_math_StringFormat_F, nNumDigitsAfterDecimal, decSep,
+ bGroupDigits ? localeData.getDigitGrouping().getConstArray() : nullptr,
+ localeData.getNumThousandSep().toChar());
+
+ if (!bIncludeLeadingDigit && aResult.getLength() > 1)
+ aResult.stripStart('0');
+
+ if (nNumDigitsAfterDecimal > 0)
+ {
+ const sal_Int32 nSepPos = aResult.indexOf(decSep);
+
+ // VBA allows up to 255 digits; rtl::math::doubleToUString outputs up to 15 digits
+ // for ~small numbers, so pad them as appropriate.
+ if (nSepPos >= 0)
+ comphelper::string::padToLength(aResult, nSepPos + nNumDigitsAfterDecimal + 1, '0');
+ }
+
+ if (bNegative)
+ {
+ if (bUseParensForNegativeNumbers)
+ aResult.insert(0, '(').append(')');
+ else
+ aResult.insert(0, '-');
+ }
+ if (isPercent)
+ aResult.append('%');
+ rPar.Get(0)->PutString(aResult.makeStringAndClear());
+}
+
+// https://docs.microsoft.com/en-us/office/vba/Language/Reference/User-Interface-Help/formatnumber-function
+void SbRtl_FormatNumber(StarBASIC*, SbxArray& rPar, bool)
+{
+ return lcl_FormatNumberPercent(rPar, false);
+}
+
+// https://docs.microsoft.com/en-us/office/vba/Language/Reference/User-Interface-Help/formatpercent-function
+void SbRtl_FormatPercent(StarBASIC*, SbxArray& rPar, bool)
+{
+ return lcl_FormatNumberPercent(rPar, true);
+}
+
+namespace {
+
+// note: BASIC does not use comphelper::random, because
+// Randomize(int) must be supported and should not affect non-BASIC random use
+struct RandomNumberGenerator
+{
+ std::mt19937 global_rng;
+
+ RandomNumberGenerator()
+ {
+ try
+ {
+ std::random_device rd;
+ // initialises the state of the global random number generator
+ // should only be called once.
+ // (note, a few std::variate_generator<> (like normal) have their
+ // own state which would need a reset as well to guarantee identical
+ // sequence of numbers, e.g. via myrand.distribution().reset())
+ global_rng.seed(rd() ^ time(nullptr));
+ }
+ catch (std::runtime_error& e)
+ {
+ SAL_WARN("basic", "Using std::random_device failed: " << e.what());
+ global_rng.seed(time(nullptr));
+ }
+ }
+};
+
+RandomNumberGenerator& theRandomNumberGenerator()
+{
+ static RandomNumberGenerator theGenerator;
+ return theGenerator;
+}
+
+}
+
+void SbRtl_Randomize(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() > 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ if (rPar.Count() == 2)
+ {
+ int nSeed = static_cast<int>(rPar.Get(1)->GetInteger());
+ theRandomNumberGenerator().global_rng.seed(nSeed);
+ }
+ // without parameter, no need to do anything - RNG is seeded at first use
+}
+
+void SbRtl_Rnd(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() > 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ std::uniform_real_distribution<double> dist(0.0, 1.0);
+ double const tmp(dist(theRandomNumberGenerator().global_rng));
+ rPar.Get(0)->PutDouble(tmp);
+ }
+}
+
+
+// Syntax: Shell("Path",[ Window-Style,[ "Params", [ bSync = sal_False ]]])
+// WindowStyles (VBA compatible):
+// 2 == Minimized
+// 3 == Maximized
+// 10 == Full-Screen (text mode applications OS/2, WIN95, WNT)
+// HACK: The WindowStyle will be passed to
+// Application::StartApp in Creator. Format: "xxxx2"
+
+
+void SbRtl_Shell(StarBASIC *, SbxArray & rPar, bool)
+{
+ const sal_uInt32 nArgCount = rPar.Count();
+ if ( nArgCount < 2 || nArgCount > 5 )
+ {
+ rPar.Get(0)->PutLong(0);
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ oslProcessOption nOptions = osl_Process_SEARCHPATH | osl_Process_DETACHED;
+
+ OUString aCmdLine = rPar.Get(1)->GetOUString();
+ // attach additional parameters - everything must be parsed anyway
+ if( nArgCount >= 4 )
+ {
+ OUString tmp = rPar.Get(3)->GetOUString().trim();
+ if (!tmp.isEmpty())
+ {
+ aCmdLine += " " + tmp;
+ }
+ }
+ else if( aCmdLine.isEmpty() )
+ {
+ // avoid special treatment (empty list)
+ aCmdLine += " ";
+ }
+ sal_Int32 nLen = aCmdLine.getLength();
+
+ // #55735 if there are parameters, they have to be separated
+ // #72471 also separate the single parameters
+ std::vector<OUString> aTokenVector;
+ OUString aToken;
+ sal_Int32 i = 0;
+ sal_Unicode c;
+ while( i < nLen )
+ {
+ for ( ;; ++i )
+ {
+ c = aCmdLine[ i ];
+ if ( c != ' ' && c != '\t' )
+ {
+ break;
+ }
+ }
+
+ if( c == '\"' || c == '\'' )
+ {
+ sal_Int32 iFoundPos = aCmdLine.indexOf( c, i + 1 );
+
+ if( iFoundPos < 0 )
+ {
+ aToken = aCmdLine.copy( i);
+ i = nLen;
+ }
+ else
+ {
+ aToken = aCmdLine.copy( i + 1, (iFoundPos - i - 1) );
+ i = iFoundPos + 1;
+ }
+ }
+ else
+ {
+ sal_Int32 iFoundSpacePos = aCmdLine.indexOf( ' ', i );
+ sal_Int32 iFoundTabPos = aCmdLine.indexOf( '\t', i );
+ sal_Int32 iFoundPos = iFoundSpacePos >= 0 ? iFoundTabPos >= 0 ? std::min( iFoundSpacePos, iFoundTabPos ) : iFoundSpacePos : -1;
+
+ if( iFoundPos < 0 )
+ {
+ aToken = aCmdLine.copy( i );
+ i = nLen;
+ }
+ else
+ {
+ aToken = aCmdLine.copy( i, (iFoundPos - i) );
+ i = iFoundPos;
+ }
+ }
+
+ // insert into the list
+ aTokenVector.push_back( aToken );
+ }
+ // #55735 / #72471 end
+
+ sal_Int16 nWinStyle = 0;
+ if( nArgCount >= 3 )
+ {
+ nWinStyle = rPar.Get(2)->GetInteger();
+ switch( nWinStyle )
+ {
+ case 2:
+ nOptions |= osl_Process_MINIMIZED;
+ break;
+ case 3:
+ nOptions |= osl_Process_MAXIMIZED;
+ break;
+ case 10:
+ nOptions |= osl_Process_FULLSCREEN;
+ break;
+ }
+
+ bool bSync = false;
+ if( nArgCount >= 5 )
+ {
+ bSync = rPar.Get(4)->GetBool();
+ }
+ if( bSync )
+ {
+ nOptions |= osl_Process_WAIT;
+ }
+ }
+
+ // #72471 work parameter(s) up
+ std::vector<OUString>::const_iterator iter = aTokenVector.begin();
+ OUString aOUStrProgURL = getFullPath( *iter );
+
+ ++iter;
+
+ sal_uInt16 nParamCount = sal::static_int_cast< sal_uInt16 >(aTokenVector.size() - 1 );
+ std::unique_ptr<rtl_uString*[]> pParamList;
+ if( nParamCount )
+ {
+ pParamList.reset( new rtl_uString*[nParamCount]);
+ for(int iVector = 0; iter != aTokenVector.end(); ++iVector, ++iter)
+ {
+ const OUString& rParamStr = *iter;
+ pParamList[iVector] = nullptr;
+ rtl_uString_assign(&(pParamList[iVector]), rParamStr.pData);
+ }
+ }
+
+ oslProcess pApp;
+ bool bSucc = osl_executeProcess(
+ aOUStrProgURL.pData,
+ pParamList.get(),
+ nParamCount,
+ nOptions,
+ nullptr,
+ nullptr,
+ nullptr, 0,
+ &pApp ) == osl_Process_E_None;
+
+ // 53521 only free process handle on success
+ if (bSucc)
+ {
+ osl_freeProcessHandle( pApp );
+ }
+
+ for(int j = 0; j < nParamCount; ++j)
+ {
+ rtl_uString_release(pParamList[j]);
+ }
+
+ if( !bSucc )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
+ }
+ else
+ {
+ rPar.Get(0)->PutLong(0);
+ }
+ }
+}
+
+void SbRtl_VarType(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() != 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ SbxDataType eType = rPar.Get(1)->GetType();
+ rPar.Get(0)->PutInteger(static_cast<sal_Int16>(eType));
+ }
+}
+
+// Exported function
+OUString getBasicTypeName( SbxDataType eType )
+{
+ static const char* pTypeNames[] =
+ {
+ "Empty", // SbxEMPTY
+ "Null", // SbxNULL
+ "Integer", // SbxINTEGER
+ "Long", // SbxLONG
+ "Single", // SbxSINGLE
+ "Double", // SbxDOUBLE
+ "Currency", // SbxCURRENCY
+ "Date", // SbxDATE
+ "String", // SbxSTRING
+ "Object", // SbxOBJECT
+ "Error", // SbxERROR
+ "Boolean", // SbxBOOL
+ "Variant", // SbxVARIANT
+ "DataObject", // SbxDATAOBJECT
+ "Unknown Type",
+ "Unknown Type",
+ "Char", // SbxCHAR
+ "Byte", // SbxBYTE
+ "UShort", // SbxUSHORT
+ "ULong", // SbxULONG
+ "Long64", // SbxLONG64
+ "ULong64", // SbxULONG64
+ "Int", // SbxINT
+ "UInt", // SbxUINT
+ "Void", // SbxVOID
+ "HResult", // SbxHRESULT
+ "Pointer", // SbxPOINTER
+ "DimArray", // SbxDIMARRAY
+ "CArray", // SbxCARRAY
+ "Userdef", // SbxUSERDEF
+ "Lpstr", // SbxLPSTR
+ "Lpwstr", // SbxLPWSTR
+ "Unknown Type", // SbxCoreSTRING
+ "WString", // SbxWSTRING
+ "WChar", // SbxWCHAR
+ "Int64", // SbxSALINT64
+ "UInt64", // SbxSALUINT64
+ "Decimal", // SbxDECIMAL
+ };
+
+ size_t nPos = static_cast<size_t>(eType) & 0x0FFF;
+ const size_t nTypeNameCount = std::size( pTypeNames );
+ if ( nPos >= nTypeNameCount )
+ {
+ nPos = nTypeNameCount - 1;
+ }
+ return OUString::createFromAscii(pTypeNames[nPos]);
+}
+
+static OUString getObjectTypeName( SbxVariable* pVar )
+{
+ OUString sRet( "Object" );
+ if ( pVar )
+ {
+ SbxBase* pBaseObj = pVar->GetObject();
+ if( !pBaseObj )
+ {
+ sRet = "Nothing";
+ }
+ else
+ {
+ SbUnoObject* pUnoObj = dynamic_cast<SbUnoObject*>( pVar );
+ if ( !pUnoObj )
+ {
+ pUnoObj = dynamic_cast<SbUnoObject*>( pBaseObj );
+ }
+ if ( pUnoObj )
+ {
+ Any aObj = pUnoObj->getUnoAny();
+ // For upstreaming unless we start to build oovbaapi by default
+ // we need to get detect the vba-ness of the object in some
+ // other way
+ // note: Automation objects do not support XServiceInfo
+ uno::Reference< XServiceInfo > xServInfo( aObj, uno::UNO_QUERY );
+ if ( xServInfo.is() )
+ {
+ // is this a VBA object ?
+ Sequence< OUString > sServices = xServInfo->getSupportedServiceNames();
+ if ( sServices.hasElements() )
+ {
+ sRet = sServices[ 0 ];
+ }
+ }
+ else
+ {
+ uno::Reference< bridge::oleautomation::XAutomationObject > xAutoMation( aObj, uno::UNO_QUERY );
+ if ( xAutoMation.is() )
+ {
+ uno::Reference< script::XInvocation > xInv( aObj, uno::UNO_QUERY );
+ if ( xInv.is() )
+ {
+ try
+ {
+ xInv->getValue( "$GetTypeName" ) >>= sRet;
+ }
+ catch(const Exception& )
+ {
+ }
+ }
+ }
+ }
+ sal_Int32 nDot = sRet.lastIndexOf( '.' );
+ if ( nDot != -1 && nDot < sRet.getLength() )
+ {
+ sRet = sRet.copy( nDot + 1 );
+ }
+ }
+ }
+ }
+ return sRet;
+}
+
+void SbRtl_TypeName(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() != 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ SbxDataType eType = rPar.Get(1)->GetType();
+ bool bIsArray = ( ( eType & SbxARRAY ) != 0 );
+
+ OUString aRetStr;
+ if ( SbiRuntime::isVBAEnabled() && eType == SbxOBJECT )
+ {
+ aRetStr = getObjectTypeName(rPar.Get(1));
+ }
+ else
+ {
+ aRetStr = getBasicTypeName( eType );
+ }
+ if( bIsArray )
+ {
+ aRetStr += "()";
+ }
+ rPar.Get(0)->PutString(aRetStr);
+ }
+}
+
+void SbRtl_Len(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() != 2)
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else
+ {
+ const OUString& rStr = rPar.Get(1)->GetOUString();
+ rPar.Get(0)->PutLong(rStr.getLength());
+ }
+}
+
+void SbRtl_DDEInitiate(StarBASIC *, SbxArray & rPar, bool)
+{
+ int nArgs = static_cast<int>(rPar.Count());
+ if ( nArgs != 3 )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ const OUString& rApp = rPar.Get(1)->GetOUString();
+ const OUString& rTopic = rPar.Get(2)->GetOUString();
+
+ SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
+ size_t nChannel;
+ ErrCode nDdeErr = pDDE->Initiate( rApp, rTopic, nChannel );
+ if( nDdeErr )
+ {
+ StarBASIC::Error( nDdeErr );
+ }
+ else
+ {
+ rPar.Get(0)->PutInteger(static_cast<sal_Int16>(nChannel));
+ }
+}
+
+void SbRtl_DDETerminate(StarBASIC *, SbxArray & rPar, bool)
+{
+ rPar.Get(0)->PutEmpty();
+ int nArgs = static_cast<int>(rPar.Count());
+ if ( nArgs != 2 )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ return;
+ }
+ size_t nChannel = rPar.Get(1)->GetInteger();
+ SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
+ ErrCode nDdeErr = pDDE->Terminate( nChannel );
+ if( nDdeErr )
+ {
+ StarBASIC::Error( nDdeErr );
+ }
+}
+
+void SbRtl_DDETerminateAll(StarBASIC *, SbxArray & rPar, bool)
+{
+ rPar.Get(0)->PutEmpty();
+ int nArgs = static_cast<int>(rPar.Count());
+ if ( nArgs != 1 )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
+ ErrCode nDdeErr = pDDE->TerminateAll();
+ if( nDdeErr )
+ {
+ StarBASIC::Error( nDdeErr );
+ }
+}
+
+void SbRtl_DDERequest(StarBASIC *, SbxArray & rPar, bool)
+{
+ int nArgs = static_cast<int>(rPar.Count());
+ if ( nArgs != 3 )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ size_t nChannel = rPar.Get(1)->GetInteger();
+ const OUString& rItem = rPar.Get(2)->GetOUString();
+ SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
+ OUString aResult;
+ ErrCode nDdeErr = pDDE->Request( nChannel, rItem, aResult );
+ if( nDdeErr )
+ {
+ StarBASIC::Error( nDdeErr );
+ }
+ else
+ {
+ rPar.Get(0)->PutString(aResult);
+ }
+}
+
+void SbRtl_DDEExecute(StarBASIC *, SbxArray & rPar, bool)
+{
+ rPar.Get(0)->PutEmpty();
+ int nArgs = static_cast<int>(rPar.Count());
+ if ( nArgs != 3 )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ size_t nChannel = rPar.Get(1)->GetInteger();
+ const OUString& rCommand = rPar.Get(2)->GetOUString();
+ SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
+ ErrCode nDdeErr = pDDE->Execute( nChannel, rCommand );
+ if( nDdeErr )
+ {
+ StarBASIC::Error( nDdeErr );
+ }
+}
+
+void SbRtl_DDEPoke(StarBASIC *, SbxArray & rPar, bool)
+{
+ rPar.Get(0)->PutEmpty();
+ int nArgs = static_cast<int>(rPar.Count());
+ if ( nArgs != 4 )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ size_t nChannel = rPar.Get(1)->GetInteger();
+ const OUString& rItem = rPar.Get(2)->GetOUString();
+ const OUString& rData = rPar.Get(3)->GetOUString();
+ SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
+ ErrCode nDdeErr = pDDE->Poke( nChannel, rItem, rData );
+ if( nDdeErr )
+ {
+ StarBASIC::Error( nDdeErr );
+ }
+}
+
+
+void SbRtl_FreeFile(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() != 1)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
+ short nChannel = 1;
+ while( nChannel < CHANNELS )
+ {
+ SbiStream* pStrm = pIO->GetStream( nChannel );
+ if( !pStrm )
+ {
+ rPar.Get(0)->PutInteger(nChannel);
+ return;
+ }
+ nChannel++;
+ }
+ StarBASIC::Error( ERRCODE_BASIC_TOO_MANY_FILES );
+}
+
+void SbRtl_LBound(StarBASIC *, SbxArray & rPar, bool)
+{
+ const sal_uInt32 nParCount = rPar.Count();
+ if ( nParCount != 3 && nParCount != 2 )
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ SbxBase* pParObj = rPar.Get(1)->GetObject();
+ SbxDimArray* pArr = dynamic_cast<SbxDimArray*>( pParObj );
+ if( !pArr )
+ return StarBASIC::Error( ERRCODE_BASIC_MUST_HAVE_DIMS );
+
+ sal_Int32 nLower, nUpper;
+ short nDim = (nParCount == 3) ? static_cast<short>(rPar.Get(2)->GetInteger()) : 1;
+ if (!pArr->GetDim(nDim, nLower, nUpper))
+ return StarBASIC::Error( ERRCODE_BASIC_OUT_OF_RANGE );
+ rPar.Get(0)->PutLong(nLower);
+}
+
+void SbRtl_UBound(StarBASIC *, SbxArray & rPar, bool)
+{
+ const sal_uInt32 nParCount = rPar.Count();
+ if ( nParCount != 3 && nParCount != 2 )
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ SbxBase* pParObj = rPar.Get(1)->GetObject();
+ SbxDimArray* pArr = dynamic_cast<SbxDimArray*>( pParObj );
+ if( !pArr )
+ return StarBASIC::Error( ERRCODE_BASIC_MUST_HAVE_DIMS );
+
+ sal_Int32 nLower, nUpper;
+ short nDim = (nParCount == 3) ? static_cast<short>(rPar.Get(2)->GetInteger()) : 1;
+ if (!pArr->GetDim(nDim, nLower, nUpper))
+ return StarBASIC::Error( ERRCODE_BASIC_OUT_OF_RANGE );
+ rPar.Get(0)->PutLong(nUpper);
+}
+
+void SbRtl_RGB(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() != 4)
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+
+ sal_Int32 nRed = rPar.Get(1)->GetInteger() & 0xFF;
+ sal_Int32 nGreen = rPar.Get(2)->GetInteger() & 0xFF;
+ sal_Int32 nBlue = rPar.Get(3)->GetInteger() & 0xFF;
+ sal_Int32 nRGB;
+
+ SbiInstance* pInst = GetSbData()->pInst;
+ bool bCompatibility = ( pInst && pInst->IsCompatibility() );
+ // See discussion in tdf#145725, here's the quotation from a link indicated in the bugtracker
+ // which explains why we need to manage RGB differently according to VB compatibility
+ // "In other words, the individual color components are stored in the opposite order one would expect.
+ // VB stores the red color component in the low-order byte of the long integer's low-order word,
+ // the green color in the high-order byte of the low-order word, and the blue color in the low-order byte of the high-order word"
+ if( bCompatibility )
+ {
+ nRGB = (nBlue << 16) | (nGreen << 8) | nRed;
+ }
+ else
+ {
+ nRGB = (nRed << 16) | (nGreen << 8) | nBlue;
+ }
+ rPar.Get(0)->PutLong(nRGB);
+}
+
+void SbRtl_QBColor(StarBASIC *, SbxArray & rPar, bool)
+{
+ static const sal_Int32 pRGB[] =
+ {
+ 0x000000,
+ 0x800000,
+ 0x008000,
+ 0x808000,
+ 0x000080,
+ 0x800080,
+ 0x008080,
+ 0xC0C0C0,
+ 0x808080,
+ 0xFF0000,
+ 0x00FF00,
+ 0xFFFF00,
+ 0x0000FF,
+ 0xFF00FF,
+ 0x00FFFF,
+ 0xFFFFFF,
+ };
+
+ if (rPar.Count() != 2)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ sal_Int16 nCol = rPar.Get(1)->GetInteger();
+ if( nCol < 0 || nCol > 15 )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ sal_Int32 nRGB = pRGB[ nCol ];
+ rPar.Get(0)->PutLong(nRGB);
+}
+
+static std::vector<sal_uInt8> byteArray2Vec(SbxArray* pArr)
+{
+ std::vector<sal_uInt8> result;
+ if (pArr)
+ {
+ const sal_uInt32 nCount = pArr->Count();
+ result.reserve(nCount + 1); // to avoid reallocation when padding in vbFromUnicode
+ for (sal_uInt32 i = 0; i < nCount; i++)
+ result.push_back(pArr->Get(i)->GetByte());
+ }
+ return result;
+}
+
+// Makes sure to get the byte array if passed, or the string converted to the bytes using
+// StringToByteArray in basic/source/sbx/sbxstr.cxx
+static std::vector<sal_uInt8> getByteArray(SbxValue& val)
+{
+ if (val.GetFullType() == SbxOBJECT)
+ if (auto pObj = val.GetObject())
+ if (pObj->GetType() == (SbxARRAY | SbxBYTE))
+ if (auto pArr = dynamic_cast<SbxArray*>(pObj))
+ return byteArray2Vec(pArr);
+
+ // Convert to string
+ tools::SvRef<SbxValue> pStringValue(new SbxValue(SbxSTRING));
+ *pStringValue = val;
+
+ // Convert string to byte array
+ tools::SvRef<SbxValue> pValue(new SbxValue(SbxOBJECT));
+ pValue->PutObject(new SbxArray(SbxBYTE));
+ *pValue = *pStringValue; // Does the magic of conversion of strings to byte arrays
+ return byteArray2Vec(dynamic_cast<SbxArray*>(pValue->GetObject()));
+}
+
+// StrConv(string, conversion, LCID)
+void SbRtl_StrConv(StarBASIC *, SbxArray & rPar, bool)
+{
+ const sal_uInt32 nArgCount = rPar.Count() - 1;
+ if( nArgCount < 2 || nArgCount > 3 )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ sal_Int32 nConversion = rPar.Get(2)->GetLong();
+ LanguageType nLanguage = LANGUAGE_SYSTEM;
+ if (nArgCount == 3)
+ {
+ sal_Int32 lcid = rPar.Get(3)->GetLong();
+ nLanguage = LanguageType(lcid);
+ }
+
+ if (nConversion == ooo::vba::VbStrConv::vbUnicode) // This mode does not combine
+ {
+ // Assume that the passed byte array is encoded in the defined encoding, convert to
+ // UTF-16 and store as string. Passed strings are converted to byte array first.
+ auto inArray = getByteArray(*rPar.Get(1));
+ std::string_view s(reinterpret_cast<char*>(inArray.data()), inArray.size() / sizeof(char));
+ const auto encoding = utl_getWinTextEncodingFromLangStr(LanguageTag(nLanguage).getBcp47());
+ OUString aOUStr = OStringToOUString(s, encoding);
+ rPar.Get(0)->PutString(aOUStr);
+ return;
+ }
+
+ if (nConversion == ooo::vba::VbStrConv::vbFromUnicode) // This mode does not combine
+ {
+ // Assume that the passed byte array is UTF-16-encoded (system-endian), convert to specified
+ // encoding and store as byte array. Passed strings are converted to byte array first.
+ auto inArray = getByteArray(*rPar.Get(1));
+ while (inArray.size() % sizeof(sal_Unicode))
+ inArray.push_back('\0');
+ std::u16string_view s(reinterpret_cast<sal_Unicode*>(inArray.data()),
+ inArray.size() / sizeof(sal_Unicode));
+ const auto encoding = utl_getWinTextEncodingFromLangStr(LanguageTag(nLanguage).getBcp47());
+ OString aOStr = OUStringToOString(s, encoding);
+ const sal_Int32 lb = IsBaseIndexOne() ? 1 : 0;
+ const sal_Int32 ub = lb + aOStr.getLength() - 1;
+ SbxDimArray* pArray = new SbxDimArray(SbxBYTE);
+ pArray->unoAddDim(lb, ub);
+
+ for (sal_Int32 i = 0; i < aOStr.getLength(); ++i)
+ {
+ SbxVariable* pNew = new SbxVariable(SbxBYTE);
+ pNew->PutByte(aOStr[i]);
+ pArray->Put(pNew, i);
+ }
+
+ SbxVariable* retVar = rPar.Get(0);
+ SbxFlagBits nFlags = retVar->GetFlags();
+ retVar->ResetFlag(SbxFlagBits::Fixed);
+ retVar->PutObject(pArray);
+ retVar->SetFlags(nFlags);
+ retVar->SetParameters(nullptr);
+ return;
+ }
+
+ std::vector<TransliterationFlags> aTranslitSet;
+ auto check = [&nConversion, &aTranslitSet](sal_Int32 conv, TransliterationFlags flag)
+ {
+ if ((nConversion & conv) != conv)
+ return false;
+
+ aTranslitSet.push_back(flag);
+ nConversion &= ~conv;
+ return true;
+ };
+
+ // Check mutually exclusive bits together
+
+ if (!check(ooo::vba::VbStrConv::vbProperCase, TransliterationFlags::TITLE_CASE))
+ if (!check(ooo::vba::VbStrConv::vbUpperCase, TransliterationFlags::LOWERCASE_UPPERCASE))
+ check(ooo::vba::VbStrConv::vbLowerCase, TransliterationFlags::UPPERCASE_LOWERCASE);
+
+ if (!check(ooo::vba::VbStrConv::vbWide, TransliterationFlags::HALFWIDTH_FULLWIDTH))
+ check(ooo::vba::VbStrConv::vbNarrow, TransliterationFlags::FULLWIDTH_HALFWIDTH);
+
+ if (!check(ooo::vba::VbStrConv::vbKatakana, TransliterationFlags::HIRAGANA_KATAKANA))
+ check(ooo::vba::VbStrConv::vbHiragana, TransliterationFlags::KATAKANA_HIRAGANA);
+
+ if (nConversion) // unknown / incorrectly combined bits
+ return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
+
+ OUString aStr = rPar.Get(1)->GetOUString();
+ if (!aStr.isEmpty() && !aTranslitSet.empty())
+ {
+ uno::Reference< uno::XComponentContext > xContext = getProcessComponentContext();
+
+ for (auto transliterationFlag : aTranslitSet)
+ {
+ if (transliterationFlag == TransliterationFlags::TITLE_CASE)
+ {
+ // TransliterationWrapper only handles the first character of the passed string
+ // when handling TITLE_CASE; see Transliteration_titlecase::transliterateImpl in
+ // i18npool/source/transliteration/transliteration_body.cxx
+ CharClass aCharClass{ xContext, LanguageTag(nLanguage) };
+ aStr = aCharClass.titlecase(aCharClass.lowercase(aStr));
+ }
+ else
+ {
+ utl::TransliterationWrapper aWrapper(xContext, transliterationFlag);
+ aStr = aWrapper.transliterate(aStr, nLanguage, 0, aStr.getLength(), nullptr);
+ }
+ }
+ }
+
+ rPar.Get(0)->PutString(aStr);
+}
+
+
+void SbRtl_Beep(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() != 1)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ Sound::Beep();
+}
+
+void SbRtl_Load(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() != 2)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+
+ SbxBase* pObj = rPar.Get(1)->GetObject();
+ if ( !pObj )
+ return;
+
+ if (SbUserFormModule* pModule = dynamic_cast<SbUserFormModule*>(pObj))
+ {
+ pModule->Load();
+ }
+ else if (SbxObject* pSbxObj = dynamic_cast<SbxObject*>(pObj))
+ {
+ SbxVariable* pVar = pSbxObj->Find("Load", SbxClassType::Method);
+ if( pVar )
+ {
+ pVar->GetInteger();
+ }
+ }
+}
+
+void SbRtl_Unload(StarBASIC *, SbxArray & rPar, bool)
+{
+ rPar.Get(0)->PutEmpty();
+ if (rPar.Count() != 2)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+
+ SbxBase* pObj = rPar.Get(1)->GetObject();
+ if ( !pObj )
+ return;
+
+ if (SbUserFormModule* pFormModule = dynamic_cast<SbUserFormModule*>(pObj))
+ {
+ pFormModule->Unload();
+ }
+ else if (SbxObject *pSbxObj = dynamic_cast<SbxObject*>(pObj))
+ {
+ SbxVariable* pVar = pSbxObj->Find("Unload", SbxClassType::Method);
+ if( pVar )
+ {
+ pVar->GetInteger();
+ }
+ }
+}
+
+void SbRtl_LoadPicture(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() != 2)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ OUString aFileURL = getFullPath(rPar.Get(1)->GetOUString());
+ std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream( aFileURL, StreamMode::READ ));
+ if( pStream )
+ {
+ Bitmap aBmp;
+ ReadDIB(aBmp, *pStream, true);
+ BitmapEx aBitmapEx(aBmp);
+ Graphic aGraphic(aBitmapEx);
+
+ SbxObjectRef xRef = new SbStdPicture;
+ static_cast<SbStdPicture*>(xRef.get())->SetGraphic( aGraphic );
+ rPar.Get(0)->PutObject(xRef.get());
+ }
+}
+
+void SbRtl_SavePicture(StarBASIC *, SbxArray & rPar, bool)
+{
+ rPar.Get(0)->PutEmpty();
+ if (rPar.Count() != 3)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ SbxBase* pObj = rPar.Get(1)->GetObject();
+ if (SbStdPicture *pPicture = dynamic_cast<SbStdPicture*>(pObj))
+ {
+ SvFileStream aOStream(rPar.Get(2)->GetOUString(), StreamMode::WRITE | StreamMode::TRUNC);
+ const Graphic& aGraphic = pPicture->GetGraphic();
+ TypeSerializer aSerializer(aOStream);
+ aSerializer.writeGraphic(aGraphic);
+ }
+}
+
+void SbRtl_MsgBox(StarBASIC *, SbxArray & rPar, bool)
+{
+ const sal_uInt32 nArgCount = rPar.Count();
+ if( nArgCount < 2 || nArgCount > 6 )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ // tdf#147529 - check for missing parameters
+ if (IsMissing(rPar, 1))
+ {
+ return StarBASIC::Error(ERRCODE_BASIC_NOT_OPTIONAL);
+ }
+
+ // tdf#151012 - initialize optional parameters with their default values (number of buttons)
+ WinBits nType = static_cast<WinBits>(GetOptionalIntegerParamOrDefault(rPar, 2, 0)); // MB_OK
+ WinBits nStyle = nType;
+ nStyle &= 15; // delete bits 4-16
+ if (nStyle > 5)
+ nStyle = 0;
+
+ enum BasicResponse
+ {
+ Ok = 1,
+ Cancel = 2,
+ Abort = 3,
+ Retry = 4,
+ Ignore = 5,
+ Yes = 6,
+ No = 7
+ };
+
+ OUString aMsg = rPar.Get(1)->GetOUString();
+ // tdf#151012 - initialize optional parameters with their default values (title of dialog box)
+ OUString aTitle = GetOptionalOUStringParamOrDefault(rPar, 3, Application::GetDisplayName());
+
+ WinBits nDialogType = nType & (16+32+64);
+
+ SolarMutexGuard aSolarGuard;
+ weld::Widget* pParent = Application::GetDefDialogParent();
+
+ VclMessageType eType = VclMessageType::Other;
+
+ switch (nDialogType)
+ {
+ case 16:
+ eType = VclMessageType::Error;
+ break;
+ case 32:
+ eType = VclMessageType::Question;
+ break;
+ case 48:
+ eType = VclMessageType::Warning;
+ break;
+ case 64:
+ eType = VclMessageType::Info;
+ break;
+ }
+
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent,
+ eType, VclButtonsType::NONE, aMsg, GetpApp()));
+
+ switch (nStyle)
+ {
+ case 0: // MB_OK
+ default:
+ xBox->add_button(GetStandardText(StandardButtonType::OK), BasicResponse::Ok);
+ break;
+ case 1: // MB_OKCANCEL
+ xBox->add_button(GetStandardText(StandardButtonType::OK), BasicResponse::Ok);
+ xBox->add_button(GetStandardText(StandardButtonType::Cancel), BasicResponse::Cancel);
+
+ if (nType & 256 || nType & 512)
+ xBox->set_default_response(BasicResponse::Cancel);
+ else
+ xBox->set_default_response(BasicResponse::Ok);
+
+ break;
+ case 2: // MB_ABORTRETRYIGNORE
+ xBox->add_button(GetStandardText(StandardButtonType::Abort), BasicResponse::Abort);
+ xBox->add_button(GetStandardText(StandardButtonType::Retry), BasicResponse::Retry);
+ xBox->add_button(GetStandardText(StandardButtonType::Ignore), BasicResponse::Ignore);
+
+ if (nType & 256)
+ xBox->set_default_response(BasicResponse::Retry);
+ else if (nType & 512)
+ xBox->set_default_response(BasicResponse::Ignore);
+ else
+ xBox->set_default_response(BasicResponse::Cancel);
+
+ break;
+ case 3: // MB_YESNOCANCEL
+ xBox->add_button(GetStandardText(StandardButtonType::Yes), BasicResponse::Yes);
+ xBox->add_button(GetStandardText(StandardButtonType::No), BasicResponse::No);
+ xBox->add_button(GetStandardText(StandardButtonType::Cancel), BasicResponse::Cancel);
+
+ if (nType & 256 || nType & 512)
+ xBox->set_default_response(BasicResponse::Cancel);
+ else
+ xBox->set_default_response(BasicResponse::Yes);
+
+ break;
+ case 4: // MB_YESNO
+ xBox->add_button(GetStandardText(StandardButtonType::Yes), BasicResponse::Yes);
+ xBox->add_button(GetStandardText(StandardButtonType::No), BasicResponse::No);
+
+ if (nType & 256 || nType & 512)
+ xBox->set_default_response(BasicResponse::No);
+ else
+ xBox->set_default_response(BasicResponse::Yes);
+
+ break;
+ case 5: // MB_RETRYCANCEL
+ xBox->add_button(GetStandardText(StandardButtonType::Retry), BasicResponse::Retry);
+ xBox->add_button(GetStandardText(StandardButtonType::Cancel), BasicResponse::Cancel);
+
+ if (nType & 256 || nType & 512)
+ xBox->set_default_response(BasicResponse::Cancel);
+ else
+ xBox->set_default_response(BasicResponse::Retry);
+
+ break;
+ }
+
+ xBox->set_title(aTitle);
+ sal_Int16 nRet = xBox->run();
+ rPar.Get(0)->PutInteger(nRet);
+}
+
+void SbRtl_SetAttr(StarBASIC *, SbxArray & rPar, bool)
+{
+ rPar.Get(0)->PutEmpty();
+ if (rPar.Count() == 3)
+ {
+ OUString aStr = rPar.Get(1)->GetOUString();
+ SbAttributes nFlags = static_cast<SbAttributes>(rPar.Get(2)->GetInteger());
+
+ if( hasUno() )
+ {
+ const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
+ if( xSFI.is() )
+ {
+ try
+ {
+ bool bReadOnly = bool(nFlags & SbAttributes::READONLY);
+ xSFI->setReadOnly( aStr, bReadOnly );
+ bool bHidden = bool(nFlags & SbAttributes::HIDDEN);
+ xSFI->setHidden( aStr, bHidden );
+ }
+ catch(const Exception & )
+ {
+ StarBASIC::Error( ERRCODE_IO_GENERAL );
+ }
+ }
+ }
+ }
+ else
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+}
+
+void SbRtl_Reset(StarBASIC *, SbxArray &, bool)
+{
+ SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
+ if (pIO)
+ {
+ pIO->CloseAll();
+ }
+}
+
+void SbRtl_DumpAllObjects(StarBASIC * pBasic, SbxArray & rPar, bool)
+{
+ const sal_uInt32 nArgCount = rPar.Count();
+ if( nArgCount < 2 || nArgCount > 3 )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+ else if( !pBasic )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_INTERNAL_ERROR );
+ }
+ else
+ {
+ SbxObject* p = pBasic;
+ while( p->GetParent() )
+ {
+ p = p->GetParent();
+ }
+ SvFileStream aStrm(rPar.Get(1)->GetOUString(),
+ StreamMode::WRITE | StreamMode::TRUNC );
+ p->Dump(aStrm, rPar.Get(2)->GetBool());
+ aStrm.Close();
+ if( aStrm.GetError() != ERRCODE_NONE )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_IO_ERROR );
+ }
+ }
+}
+
+
+void SbRtl_FileExists(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() == 2)
+ {
+ OUString aStr = rPar.Get(1)->GetOUString();
+ bool bExists = false;
+
+ if( hasUno() )
+ {
+ const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
+ if( xSFI.is() )
+ {
+ try
+ {
+ bExists = xSFI->exists( aStr );
+ }
+ catch(const Exception & )
+ {
+ StarBASIC::Error( ERRCODE_IO_GENERAL );
+ }
+ }
+ }
+ else
+ {
+ DirectoryItem aItem;
+ FileBase::RC nRet = DirectoryItem::get( getFullPath( aStr ), aItem );
+ bExists = (nRet == FileBase::E_None);
+ }
+ rPar.Get(0)->PutBool(bExists);
+ }
+ else
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+}
+
+void SbRtl_Partition(StarBASIC *, SbxArray & rPar, bool)
+{
+ if (rPar.Count() != 5)
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ sal_Int32 nNumber = rPar.Get(1)->GetLong();
+ sal_Int32 nStart = rPar.Get(2)->GetLong();
+ sal_Int32 nStop = rPar.Get(3)->GetLong();
+ sal_Int32 nInterval = rPar.Get(4)->GetLong();
+
+ if( nStart < 0 || nStop <= nStart || nInterval < 1 )
+ {
+ return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ }
+
+ // the Partition function inserts leading spaces before lowervalue and uppervalue
+ // so that they both have the same number of characters as the string
+ // representation of the value (Stop + 1). This ensures that if you use the output
+ // of the Partition function with several values of Number, the resulting text
+ // will be handled properly during any subsequent sort operation.
+
+ // calculate the maximum number of characters before lowervalue and uppervalue
+ OUString aBeforeStart = OUString::number( nStart - 1 );
+ OUString aAfterStop = OUString::number( nStop + 1 );
+ sal_Int32 nLen1 = aBeforeStart.getLength();
+ sal_Int32 nLen2 = aAfterStop.getLength();
+ sal_Int32 nLen = nLen1 >= nLen2 ? nLen1:nLen2;
+
+ OUStringBuffer aRetStr( nLen * 2 + 1);
+ OUString aLowerValue;
+ OUString aUpperValue;
+ if( nNumber < nStart )
+ {
+ aUpperValue = aBeforeStart;
+ }
+ else if( nNumber > nStop )
+ {
+ aLowerValue = aAfterStop;
+ }
+ else
+ {
+ sal_Int32 nLowerValue = nNumber;
+ sal_Int32 nUpperValue = nLowerValue;
+ if( nInterval > 1 )
+ {
+ nLowerValue = ((( nNumber - nStart ) / nInterval ) * nInterval ) + nStart;
+ nUpperValue = nLowerValue + nInterval - 1;
+ }
+ aLowerValue = OUString::number( nLowerValue );
+ aUpperValue = OUString::number( nUpperValue );
+ }
+
+ nLen1 = aLowerValue.getLength();
+ nLen2 = aUpperValue.getLength();
+
+ if( nLen > nLen1 )
+ {
+ // appending the leading spaces for the lowervalue
+ for ( sal_Int32 i= nLen - nLen1; i > 0; --i )
+ {
+ aRetStr.append(" ");
+ }
+ }
+ aRetStr.append( aLowerValue + ":");
+ if( nLen > nLen2 )
+ {
+ // appending the leading spaces for the uppervalue
+ for ( sal_Int32 i= nLen - nLen2; i > 0; --i )
+ {
+ aRetStr.append(" ");
+ }
+ }
+ aRetStr.append( aUpperValue );
+ rPar.Get(0)->PutString(aRetStr.makeStringAndClear());
+}
+
+#endif
+
+sal_Int16 implGetDateYear( double aDate )
+{
+ Date aRefDate(1899'12'30);
+ sal_Int32 nDays = static_cast<sal_Int32>(aDate);
+ aRefDate.AddDays( nDays );
+ sal_Int16 nRet = aRefDate.GetYear();
+ return nRet;
+}
+
+bool implDateSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay,
+ bool bUseTwoDigitYear, SbDateCorrection eCorr, double& rdRet )
+{
+ // XXX NOTE: For VBA years<0 are invalid and years in the range 0..29 and
+ // 30..99 can not be input as they are 2-digit for 2000..2029 and
+ // 1930..1999, VBA mode overrides bUseTwoDigitYear (as if that was always
+ // true). For VBA years > 9999 are invalid.
+ // For StarBASIC, if bUseTwoDigitYear==true then years in the range 0..99
+ // can not be input as they are 2-digit for 1900..1999, years<0 are
+ // accepted. If bUseTwoDigitYear==false then all years are accepted, but
+ // year 0 is invalid (last day BCE -0001-12-31, first day CE 0001-01-01).
+#if HAVE_FEATURE_SCRIPTING
+ if ( (nYear < 0 || 9999 < nYear) && SbiRuntime::isVBAEnabled() )
+ {
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+ return false;
+ }
+ else if ( nYear < 30 && SbiRuntime::isVBAEnabled() )
+ {
+ nYear += 2000;
+ }
+ else
+#endif
+ {
+ if ( 0 <= nYear && nYear < 100 &&
+#if HAVE_FEATURE_SCRIPTING
+ (bUseTwoDigitYear || SbiRuntime::isVBAEnabled())
+#else
+ bUseTwoDigitYear
+#endif
+ )
+ {
+ nYear += 1900;
+ }
+ }
+
+ sal_Int32 nAddMonths = 0;
+ sal_Int32 nAddDays = 0;
+ // Always sanitize values to set date and to use for validity detection.
+ if (nMonth < 1 || 12 < nMonth)
+ {
+ sal_Int16 nM = ((nMonth < 1) ? (12 + (nMonth % 12)) : (nMonth % 12));
+ nAddMonths = nMonth - nM;
+ nMonth = nM;
+ }
+ // Day 0 would already be normalized during Date::Normalize(), include
+ // it in negative days, also to detect non-validity. The actual day of
+ // month is 1+(nDay-1)
+ if (nDay < 1)
+ {
+ nAddDays = nDay - 1;
+ nDay = 1;
+ }
+ else if (nDay > 31)
+ {
+ nAddDays = nDay - 31;
+ nDay = 31;
+ }
+
+ Date aCurDate( nDay, nMonth, nYear );
+
+ /* TODO: we could enable the same rollover mechanism for StarBASIC to be
+ * compatible with VBA (just with our wider supported date range), then
+ * documentation would need to be adapted. As is, the DateSerial() runtime
+ * function works as dumb as documented... (except that the resulting date
+ * is checked for validity now and not just day<=31 and month<=12).
+ * If change wanted then simply remove overriding RollOver here and adapt
+ * documentation.*/
+#if HAVE_FEATURE_SCRIPTING
+ if (eCorr == SbDateCorrection::RollOver && !SbiRuntime::isVBAEnabled())
+ eCorr = SbDateCorrection::None;
+#endif
+
+ if (nYear == 0 || (eCorr == SbDateCorrection::None && (nAddMonths || nAddDays || !aCurDate.IsValidDate())))
+ {
+#if HAVE_FEATURE_SCRIPTING
+ StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
+#endif
+ return false;
+ }
+
+ if (eCorr != SbDateCorrection::None)
+ {
+ aCurDate.Normalize();
+ if (nAddMonths)
+ aCurDate.AddMonths( nAddMonths);
+ if (nAddDays)
+ aCurDate.AddDays( nAddDays);
+ if (eCorr == SbDateCorrection::TruncateToMonth && aCurDate.GetMonth() != nMonth)
+ {
+ if (aCurDate.GetYear() == SAL_MAX_INT16 && nMonth == 12)
+ {
+ // Roll over and back not possible, hard max.
+ aCurDate.SetMonth(12);
+ aCurDate.SetDay(31);
+ }
+ else
+ {
+ aCurDate.SetMonth(nMonth);
+ aCurDate.SetDay(1);
+ aCurDate.AddMonths(1);
+ aCurDate.AddDays(-1);
+ }
+ }
+ }
+
+ rdRet = GetDayDiff(aCurDate);
+ return true;
+}
+
+double implTimeSerial( sal_Int16 nHours, sal_Int16 nMinutes, sal_Int16 nSeconds )
+{
+ return
+ static_cast<double>( nHours * ::tools::Time::secondPerHour +
+ nMinutes * ::tools::Time::secondPerMinute +
+ nSeconds)
+ /
+ static_cast<double>( ::tools::Time::secondPerDay );
+}
+
+bool implDateTimeSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay,
+ sal_Int16 nHour, sal_Int16 nMinute, sal_Int16 nSecond,
+ double& rdRet )
+{
+ double dDate;
+ if(!implDateSerial(nYear, nMonth, nDay, false/*bUseTwoDigitYear*/, SbDateCorrection::None, dDate))
+ return false;
+ rdRet += dDate + implTimeSerial(nHour, nMinute, nSecond);
+ return true;
+}
+
+sal_Int16 implGetMinute( double dDate )
+{
+ double nFrac = dDate - floor( dDate );
+ nFrac *= 86400.0;
+ sal_Int32 nSeconds = static_cast<sal_Int32>(nFrac + 0.5);
+ sal_Int16 nTemp = static_cast<sal_Int16>(nSeconds % 3600);
+ sal_Int16 nMin = nTemp / 60;
+ return nMin;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */