xref: /core/basic/source/runtime/methods.cxx (revision c4c017d1)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <config_features.h>
21 
22 #include <tools/date.hxx>
23 #include <basic/sbxvar.hxx>
24 #include <basic/sbuno.hxx>
25 #include <osl/process.h>
26 #include <vcl/dibtools.hxx>
27 #include <vcl/svapp.hxx>
28 #include <vcl/settings.hxx>
29 #include <vcl/sound.hxx>
30 #include <vcl/wintypes.hxx>
31 #include <vcl/stdtext.hxx>
32 #include <vcl/weld.hxx>
33 #include <basic/sbx.hxx>
34 #include <svl/zforlist.hxx>
35 #include <rtl/character.hxx>
36 #include <rtl/math.hxx>
37 #include <tools/urlobj.hxx>
38 #include <osl/time.h>
39 #include <unotools/charclass.hxx>
40 #include <unotools/ucbstreamhelper.hxx>
41 #include <unotools/wincodepage.hxx>
42 #include <tools/wldcrd.hxx>
43 #include <i18nlangtag/lang.h>
44 #include <rtl/string.hxx>
45 #include <sal/log.hxx>
46 #include <comphelper/DirectoryHelper.hxx>
47 
48 #include <runtime.hxx>
49 #include <sbunoobj.hxx>
50 #include <osl/file.hxx>
51 #include <errobject.hxx>
52 
53 #include <comphelper/string.hxx>
54 #include <comphelper/processfactory.hxx>
55 
56 #include <com/sun/star/uno/Sequence.hxx>
57 #include <com/sun/star/util/DateTime.hpp>
58 #include <com/sun/star/lang/Locale.hpp>
59 #include <com/sun/star/lang/XServiceInfo.hpp>
60 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
61 #include <com/sun/star/script/XErrorQuery.hpp>
62 #include <ooo/vba/VbStrConv.hpp>
63 #include <ooo/vba/VbTriState.hpp>
64 #include <com/sun/star/bridge/oleautomation/XAutomationObject.hpp>
65 #include <memory>
66 #include <random>
67 #include <string_view>
68 #include <o3tl/char16_t2wchar_t.hxx>
69 
70 // include search util
71 #include <com/sun/star/i18n/Transliteration.hpp>
72 #include <com/sun/star/util/SearchAlgorithms2.hpp>
73 #include <i18nutil/searchopt.hxx>
74 #include <unotools/textsearch.hxx>
75 #include <svl/numformat.hxx>
76 
77 #include <date.hxx>
78 #include <sbstdobj.hxx>
79 #include <rtlproto.hxx>
80 #include <image.hxx>
81 #include <iosys.hxx>
82 #include "ddectrl.hxx"
83 #include <sbintern.hxx>
84 #include <basic/vbahelper.hxx>
85 
86 #include <vector>
87 #include <math.h>
88 #include <stdio.h>
89 #include <stdlib.h>
90 #include <errno.h>
91 
92 #include <sbobjmod.hxx>
93 #include <sbxmod.hxx>
94 
95 #ifdef _WIN32
96 #include <prewin.h>
97 #include <direct.h>
98 #include <io.h>
99 #include <postwin.h>
100 #else
101 #include <unistd.h>
102 #endif
103 
104 #include <vcl/TypeSerializer.hxx>
105 
106 using namespace comphelper;
107 using namespace osl;
108 using namespace com::sun::star;
109 using namespace com::sun::star::lang;
110 using namespace com::sun::star::uno;
111 
112 static sal_Int32 GetDayDiff(const Date& rDate) { return rDate - Date(1899'12'30); }
113 
114 #if HAVE_FEATURE_SCRIPTING
115 
116 static void FilterWhiteSpace( OUString& rStr )
117 {
118     if (rStr.isEmpty())
119     {
120         return;
121     }
122     OUStringBuffer aRet;
123 
124     for (sal_Int32 i = 0; i < rStr.getLength(); ++i)
125     {
126         sal_Unicode cChar = rStr[i];
127         if ((cChar != ' ') && (cChar != '\t') &&
128            (cChar != '\n') && (cChar != '\r'))
129         {
130             aRet.append(cChar);
131         }
132     }
133 
134     rStr = aRet.makeStringAndClear();
135 }
136 
137 static const CharClass& GetCharClass()
138 {
139     static CharClass aCharClass( Application::GetSettings().GetLanguageTag() );
140     return aCharClass;
141 }
142 
143 static bool isFolder( FileStatus::Type aType )
144 {
145     return ( aType == FileStatus::Directory || aType == FileStatus::Volume );
146 }
147 
148 
149 //*** UCB file access ***
150 
151 // Converts possibly relative paths to absolute paths
152 // according to the setting done by ChDir/ChDrive
153 OUString getFullPath( const OUString& aRelPath )
154 {
155     OUString aFileURL;
156 
157     // #80204 Try first if it already is a valid URL
158     INetURLObject aURLObj( aRelPath );
159     aFileURL = aURLObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
160 
161     if( aFileURL.isEmpty() )
162     {
163         File::getFileURLFromSystemPath( aRelPath, aFileURL );
164     }
165 
166     return aFileURL;
167 }
168 
169 // TODO: -> SbiGlobals
170 static uno::Reference< ucb::XSimpleFileAccess3 > const & getFileAccess()
171 {
172     static uno::Reference< ucb::XSimpleFileAccess3 > xSFI = ucb::SimpleFileAccess::create( comphelper::getProcessComponentContext() );
173     return xSFI;
174 }
175 
176 
177 // Properties and methods lie down the return value at the Get (bPut = sal_False) in the
178 // element 0 of the Argv; the value of element 0 is saved at Put (bPut = sal_True)
179 
180 // CreateObject( class )
181 
182 void SbRtl_CreateObject(StarBASIC * pBasic, SbxArray & rPar, bool)
183 {
184     if( rPar.Count() < 2 )
185         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
186 
187     OUString aClass(rPar.Get(1)->GetOUString());
188     SbxObjectRef p = SbxBase::CreateObject( aClass );
189     if( !p.is() )
190         return StarBASIC::Error( ERRCODE_BASIC_CANNOT_LOAD );
191 
192     // Convenience: enter BASIC as parent
193     p->SetParent( pBasic );
194     rPar.Get(0)->PutObject(p.get());
195 }
196 
197 // Error( n )
198 
199 void SbRtl_Error(StarBASIC * pBasic, SbxArray & rPar, bool)
200 {
201     if( !pBasic )
202         return StarBASIC::Error( ERRCODE_BASIC_INTERNAL_ERROR );
203 
204     OUString aErrorMsg;
205     ErrCode nErr = ERRCODE_NONE;
206     sal_Int32 nCode = 0;
207     if (rPar.Count() == 1)
208     {
209         nErr = StarBASIC::GetErrBasic();
210         aErrorMsg = StarBASIC::GetErrorMsg();
211     }
212     else
213     {
214         nCode = rPar.Get(1)->GetLong();
215         if( nCode > 65535 )
216         {
217             StarBASIC::Error( ERRCODE_BASIC_CONVERSION );
218         }
219         else
220         {
221             nErr = StarBASIC::GetSfxFromVBError( static_cast<sal_uInt16>(nCode) );
222         }
223     }
224     bool bVBA = SbiRuntime::isVBAEnabled();
225     OUString tmpErrMsg;
226     if( bVBA && !aErrorMsg.isEmpty())
227     {
228         tmpErrMsg = aErrorMsg;
229     }
230     else
231     {
232         StarBASIC::MakeErrorText( nErr, aErrorMsg );
233         tmpErrMsg = StarBASIC::GetErrorText();
234     }
235     // If this rtlfunc 'Error' passed an errcode the same as the active Err Objects's
236     // current err then  return the description for the error message if it is set
237     // ( complicated isn't it ? )
238     if (bVBA && rPar.Count() > 1)
239     {
240         uno::Reference< ooo::vba::XErrObject > xErrObj( SbxErrObject::getUnoErrObject() );
241         if ( xErrObj.is() && xErrObj->getNumber() == nCode && !xErrObj->getDescription().isEmpty() )
242         {
243             tmpErrMsg = xErrObj->getDescription();
244         }
245     }
246     rPar.Get(0)->PutString(tmpErrMsg);
247 }
248 
249 // Sinus
250 
251 void SbRtl_Sin(StarBASIC *, SbxArray & rPar, bool)
252 {
253     if (rPar.Count() < 2)
254         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
255 
256     SbxVariableRef pArg = rPar.Get(1);
257     rPar.Get(0)->PutDouble(sin(pArg->GetDouble()));
258 }
259 
260 
261 void SbRtl_Cos(StarBASIC *, SbxArray & rPar, bool)
262 {
263     if (rPar.Count() < 2)
264         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
265 
266     SbxVariableRef pArg = rPar.Get(1);
267     rPar.Get(0)->PutDouble(cos(pArg->GetDouble()));
268 }
269 
270 
271 void SbRtl_Atn(StarBASIC *, SbxArray & rPar, bool)
272 {
273     if (rPar.Count() < 2)
274         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
275 
276     SbxVariableRef pArg = rPar.Get(1);
277     rPar.Get(0)->PutDouble(atan(pArg->GetDouble()));
278 }
279 
280 
281 void SbRtl_Abs(StarBASIC *, SbxArray & rPar, bool)
282 {
283     if (rPar.Count() < 2)
284         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
285 
286     SbxVariableRef pArg = rPar.Get(1);
287     rPar.Get(0)->PutDouble(fabs(pArg->GetDouble()));
288 }
289 
290 
291 void SbRtl_Asc(StarBASIC *, SbxArray & rPar, bool)
292 {
293     if (rPar.Count() < 2)
294         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
295 
296     SbxVariableRef pArg = rPar.Get(1);
297     OUString aStr( pArg->GetOUString() );
298     if ( aStr.isEmpty())
299     {
300         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
301         rPar.Get(0)->PutEmpty();
302         return;
303     }
304     sal_Unicode aCh = aStr[0];
305     rPar.Get(0)->PutLong(aCh);
306 }
307 
308 static void implChr( SbxArray& rPar, bool bChrW )
309 {
310     if (rPar.Count() < 2)
311         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
312 
313     SbxVariableRef pArg = rPar.Get(1);
314 
315     OUString aStr;
316     if( !bChrW && SbiRuntime::isVBAEnabled() )
317     {
318         char c = static_cast<char>(pArg->GetByte());
319         aStr = OUString(&c, 1, osl_getThreadTextEncoding());
320     }
321     else
322     {
323         // Map negative 16-bit values to large positive ones, so that code like Chr(&H8000)
324         // still works after the fix for tdf#62326 changed those four-digit hex notations to
325         // produce negative values:
326         sal_Int32 aCh = pArg->GetLong();
327         if (aCh < -0x8000 || aCh > 0xFFFF)
328         {
329             StarBASIC::Error(ERRCODE_BASIC_MATH_OVERFLOW);
330             aCh = 0;
331         }
332         aStr = OUString(static_cast<sal_Unicode>(aCh));
333     }
334     rPar.Get(0)->PutString(aStr);
335 }
336 
337 void SbRtl_Chr(StarBASIC *, SbxArray & rPar, bool)
338 {
339     implChr( rPar, false/*bChrW*/ );
340 }
341 
342 void SbRtl_ChrW(StarBASIC *, SbxArray & rPar, bool)
343 {
344     implChr( rPar, true/*bChrW*/ );
345 }
346 
347 #if defined _WIN32
348 
349 namespace {
350 
351 extern "C" void invalidParameterHandler(
352     wchar_t const * expression, wchar_t const * function, wchar_t const * file, unsigned int line,
353     uintptr_t)
354 {
355     SAL_INFO(
356         "basic",
357         "invalid parameter during _wgetdcwd; \""
358             << (expression ? OUString(o3tl::toU(expression)) : OUString("???"))
359             << "\" (" << (function ? OUString(o3tl::toU(function)) : OUString("???")) << ") at "
360             << (file ? OUString(o3tl::toU(file)) : OUString("???")) << ":" << line);
361 }
362 
363 }
364 
365 #endif
366 
367 void SbRtl_CurDir(StarBASIC *, SbxArray & rPar, bool)
368 {
369     // #57064 Although this function doesn't work with DirEntry, it isn't touched
370     // by the adjustment to virtual URLs, as, using the DirEntry-functionality,
371     // there's no possibility to detect the current one in a way that a virtual URL
372     // could be delivered.
373 
374 #if defined(_WIN32)
375     int nCurDir = 0;  // Current dir // JSM
376     if (rPar.Count() == 2)
377     {
378         OUString aDrive = rPar.Get(1)->GetOUString();
379         if ( aDrive.getLength() != 1 )
380             return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
381 
382         auto c = rtl::toAsciiUpperCase(aDrive[0]);
383         if ( !rtl::isAsciiUpperCase( c ) )
384             return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
385 
386         nCurDir = c - 'A' + 1;
387     }
388     wchar_t pBuffer[ _MAX_PATH ];
389     // _wgetdcwd calls the C runtime's invalid parameter handler (which by default terminates the
390     // process) if nCurDir does not correspond to an existing drive, so temporarily set a "harmless"
391     // handler:
392     auto const handler = _set_thread_local_invalid_parameter_handler(&invalidParameterHandler);
393     auto const ok = _wgetdcwd( nCurDir, pBuffer, _MAX_PATH ) != nullptr;
394     _set_thread_local_invalid_parameter_handler(handler);
395     if ( !ok )
396         return StarBASIC::Error( ERRCODE_BASIC_NO_DEVICE );
397 
398     rPar.Get(0)->PutString(OUString(o3tl::toU(pBuffer)));
399 
400 #else
401 
402     const int PATH_INCR = 250;
403 
404     int nSize = PATH_INCR;
405     std::unique_ptr<char[]> pMem;
406     while( true )
407     {
408         pMem.reset(new char[nSize]);
409         if( !pMem )
410             return StarBASIC::Error( ERRCODE_BASIC_NO_MEMORY );
411 
412         if( getcwd( pMem.get(), nSize-1 ) != nullptr )
413         {
414             rPar.Get(0)->PutString(OUString::createFromAscii(pMem.get()));
415             return;
416         }
417         if( errno != ERANGE )
418             return StarBASIC::Error( ERRCODE_BASIC_INTERNAL_ERROR );
419 
420         nSize += PATH_INCR;
421     };
422 
423 #endif
424 }
425 
426 void SbRtl_ChDir(StarBASIC * pBasic, SbxArray & rPar, bool)
427 {
428     rPar.Get(0)->PutEmpty();
429     if (rPar.Count() == 2)
430     {
431         // VBA: track current directory per document type (separately for Writer, Calc, Impress, etc.)
432         if( SbiRuntime::isVBAEnabled() )
433         {
434             ::basic::vba::registerCurrentDirectory(getDocumentModel(pBasic),
435                                                    rPar.Get(1)->GetOUString());
436         }
437     }
438     else
439     {
440         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
441     }
442 }
443 
444 void SbRtl_ChDrive(StarBASIC *, SbxArray & rPar, bool)
445 {
446     rPar.Get(0)->PutEmpty();
447     if (rPar.Count() != 2)
448     {
449         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
450     }
451 }
452 
453 
454 // Implementation of StepRENAME with UCB
455 void implStepRenameUCB( const OUString& aSource, const OUString& aDest )
456 {
457     const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
458     if( !xSFI.is() )
459         return;
460 
461     try
462     {
463         OUString aSourceFullPath = getFullPath( aSource );
464         if( !xSFI->exists( aSourceFullPath ) )
465         {
466             StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
467             return;
468         }
469 
470         OUString aDestFullPath = getFullPath( aDest );
471         if( xSFI->exists( aDestFullPath ) )
472         {
473             StarBASIC::Error( ERRCODE_BASIC_FILE_EXISTS );
474         }
475         else
476         {
477             xSFI->move( aSourceFullPath, aDestFullPath );
478         }
479     }
480     catch(const Exception & )
481     {
482         StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
483     }
484 }
485 
486 // Implementation of StepRENAME with OSL
487 void implStepRenameOSL( const OUString& aSource, const OUString& aDest )
488 {
489     FileBase::RC nRet = File::move( getFullPath( aSource ), getFullPath( aDest ) );
490     if( nRet != FileBase::E_None )
491     {
492         StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
493     }
494 }
495 
496 void SbRtl_FileCopy(StarBASIC *, SbxArray & rPar, bool)
497 {
498     rPar.Get(0)->PutEmpty();
499     if (rPar.Count() == 3)
500     {
501         OUString aSource = rPar.Get(1)->GetOUString();
502         OUString aDest = rPar.Get(2)->GetOUString();
503         if( hasUno() )
504         {
505             const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
506             if( xSFI.is() )
507             {
508                 try
509                 {
510                     xSFI->copy( getFullPath( aSource ), getFullPath( aDest ) );
511                 }
512                 catch(const Exception & )
513                 {
514                     StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
515                 }
516             }
517         }
518         else
519         {
520             FileBase::RC nRet = File::copy( getFullPath( aSource ), getFullPath( aDest ) );
521             if( nRet != FileBase::E_None )
522             {
523                 StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
524             }
525         }
526     }
527     else
528         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
529 }
530 
531 void SbRtl_Kill(StarBASIC *, SbxArray & rPar, bool)
532 {
533     rPar.Get(0)->PutEmpty();
534     if (rPar.Count() == 2)
535     {
536         OUString aFileSpec = rPar.Get(1)->GetOUString();
537 
538         if( hasUno() )
539         {
540             const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
541             if( xSFI.is() )
542             {
543                 OUString aFullPath = getFullPath( aFileSpec );
544                 if( !xSFI->exists( aFullPath ) || xSFI->isFolder( aFullPath ) )
545                 {
546                     StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
547                     return;
548                 }
549                 try
550                 {
551                     xSFI->kill( aFullPath );
552                 }
553                 catch(const Exception & )
554                 {
555                     StarBASIC::Error( ERRCODE_IO_GENERAL );
556                 }
557             }
558         }
559         else
560         {
561             File::remove( getFullPath( aFileSpec ) );
562         }
563     }
564     else
565     {
566         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
567     }
568 }
569 
570 void SbRtl_MkDir(StarBASIC * pBasic, SbxArray & rPar, bool bWrite)
571 {
572     rPar.Get(0)->PutEmpty();
573     if (rPar.Count() == 2)
574     {
575         OUString aPath = rPar.Get(1)->GetOUString();
576         if ( SbiRuntime::isVBAEnabled() )
577         {
578             // In vba if the full path is not specified then
579             // folder is created relative to the curdir
580             INetURLObject aURLObj( getFullPath( aPath ) );
581             if ( aURLObj.GetProtocol() != INetProtocol::File )
582             {
583                 SbxArrayRef pPar = new SbxArray();
584                 SbxVariableRef pResult = new SbxVariable();
585                 SbxVariableRef pParam = new SbxVariable();
586                 pPar->Insert(pResult.get(), pPar->Count());
587                 pPar->Insert(pParam.get(), pPar->Count());
588                 SbRtl_CurDir( pBasic, *pPar, bWrite );
589 
590                 OUString sCurPathURL;
591                 File::getFileURLFromSystemPath(pPar->Get(0)->GetOUString(), sCurPathURL);
592 
593                 aURLObj.SetURL( sCurPathURL );
594                 aURLObj.Append( aPath );
595                 File::getSystemPathFromFileURL(aURLObj.GetMainURL( INetURLObject::DecodeMechanism::ToIUri  ),aPath ) ;
596             }
597         }
598 
599         if( hasUno() )
600         {
601             const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
602             if( xSFI.is() )
603             {
604                 try
605                 {
606                     xSFI->createFolder( getFullPath( aPath ) );
607                 }
608                 catch(const Exception & )
609                 {
610                     StarBASIC::Error( ERRCODE_IO_GENERAL );
611                 }
612             }
613         }
614         else
615         {
616             Directory::create( getFullPath( aPath ) );
617         }
618     }
619     else
620     {
621         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
622     }
623 }
624 
625 
626 static void implRemoveDirRecursive( const OUString& aDirPath )
627 {
628     DirectoryItem aItem;
629     FileBase::RC nRet = DirectoryItem::get( aDirPath, aItem );
630     bool bExists = (nRet == FileBase::E_None);
631 
632     FileStatus aFileStatus( osl_FileStatus_Mask_Type );
633     nRet = aItem.getFileStatus( aFileStatus );
634     bool bFolder = nRet == FileBase::E_None
635         && isFolder( aFileStatus.getFileType() );
636 
637     if( !bExists || !bFolder )
638     {
639         return StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
640     }
641 
642     Directory aDir( aDirPath );
643     nRet = aDir.open();
644     if( nRet != FileBase::E_None )
645     {
646         return StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
647     }
648     aDir.close();
649 
650     comphelper::DirectoryHelper::deleteDirRecursively(aDirPath);
651 }
652 
653 
654 void SbRtl_RmDir(StarBASIC *, SbxArray & rPar, bool)
655 {
656     rPar.Get(0)->PutEmpty();
657     if (rPar.Count() == 2)
658     {
659         OUString aPath = rPar.Get(1)->GetOUString();
660         if( hasUno() )
661         {
662             const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
663             if( xSFI.is() )
664             {
665                 try
666                 {
667                     if( !xSFI->isFolder( aPath ) )
668                     {
669                         return StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
670                     }
671                     SbiInstance* pInst = GetSbData()->pInst;
672                     bool bCompatibility = ( pInst && pInst->IsCompatibility() );
673                     if( bCompatibility )
674                     {
675                         Sequence< OUString > aContent = xSFI->getFolderContents( aPath, true );
676                         if( aContent.hasElements() )
677                         {
678                             return StarBASIC::Error( ERRCODE_BASIC_ACCESS_ERROR );
679                         }
680                     }
681 
682                     xSFI->kill( getFullPath( aPath ) );
683                 }
684                 catch(const Exception & )
685                 {
686                     StarBASIC::Error( ERRCODE_IO_GENERAL );
687                 }
688             }
689         }
690         else
691         {
692             implRemoveDirRecursive( getFullPath( aPath ) );
693         }
694     }
695     else
696     {
697         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
698     }
699 }
700 
701 void SbRtl_SendKeys(StarBASIC *, SbxArray & rPar, bool)
702 {
703     rPar.Get(0)->PutEmpty();
704     StarBASIC::Error(ERRCODE_BASIC_NOT_IMPLEMENTED);
705 }
706 
707 void SbRtl_Exp(StarBASIC *, SbxArray & rPar, bool)
708 {
709     if (rPar.Count() < 2)
710         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
711 
712     double aDouble = rPar.Get(1)->GetDouble();
713     aDouble = exp( aDouble );
714     checkArithmeticOverflow( aDouble );
715     rPar.Get(0)->PutDouble(aDouble);
716 }
717 
718 void SbRtl_FileLen(StarBASIC *, SbxArray & rPar, bool)
719 {
720     if (rPar.Count() < 2)
721     {
722         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
723     }
724 
725     SbxVariableRef pArg = rPar.Get(1);
726     OUString aStr( pArg->GetOUString() );
727     sal_Int32 nLen = 0;
728     if( hasUno() )
729     {
730         const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
731         if( xSFI.is() )
732         {
733             try
734             {
735                 nLen = xSFI->getSize( getFullPath( aStr ) );
736             }
737             catch(const Exception & )
738             {
739                 StarBASIC::Error( ERRCODE_IO_GENERAL );
740             }
741         }
742     }
743     else
744     {
745         DirectoryItem aItem;
746         (void)DirectoryItem::get( getFullPath( aStr ), aItem );
747         FileStatus aFileStatus( osl_FileStatus_Mask_FileSize );
748         (void)aItem.getFileStatus( aFileStatus );
749         nLen = static_cast<sal_Int32>(aFileStatus.getFileSize());
750     }
751     rPar.Get(0)->PutLong(nLen);
752 }
753 
754 
755 
756 void SbRtl_Hex(StarBASIC *, SbxArray & rPar, bool)
757 {
758     if (rPar.Count() < 2)
759     {
760         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
761     }
762 
763     SbxVariableRef pArg = rPar.Get(1);
764     // converting value to unsigned and limit to 2 or 4 byte representation
765     sal_uInt32 nVal = pArg->IsInteger() ?
766         static_cast<sal_uInt16>(pArg->GetInteger()) :
767         static_cast<sal_uInt32>(pArg->GetLong());
768     rPar.Get(0)->PutString(OUString::number(nVal, 16).toAsciiUpperCase());
769 }
770 
771 void SbRtl_FuncCaller(StarBASIC *, SbxArray & rPar, bool)
772 {
773     if ( SbiRuntime::isVBAEnabled() &&  GetSbData()->pInst && GetSbData()->pInst->pRun )
774     {
775         if ( GetSbData()->pInst->pRun->GetExternalCaller() )
776             *rPar.Get(0) = *GetSbData()->pInst->pRun->GetExternalCaller();
777         else
778         {
779             SbxVariableRef pVar = new SbxVariable(SbxVARIANT);
780             *rPar.Get(0) = *pVar;
781         }
782     }
783     else
784     {
785         StarBASIC::Error( ERRCODE_BASIC_NOT_IMPLEMENTED );
786     }
787 
788 }
789 // InStr( [start],string,string,[compare] )
790 
791 void SbRtl_InStr(StarBASIC *, SbxArray & rPar, bool)
792 {
793     const sal_uInt32 nArgCount = rPar.Count() - 1;
794     if ( nArgCount < 2 )
795         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
796     else
797     {
798         sal_Int32 nStartPos = 1;
799         sal_Int32 nFirstStringPos = 1;
800 
801         if ( nArgCount >= 3 )
802         {
803             nStartPos = rPar.Get(1)->GetLong();
804             if( nStartPos <= 0 )
805             {
806                 StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
807                 nStartPos = 1;
808             }
809             nFirstStringPos++;
810         }
811 
812         SbiInstance* pInst = GetSbData()->pInst;
813         bool bTextMode;
814         bool bCompatibility = ( pInst && pInst->IsCompatibility() );
815         if( bCompatibility )
816         {
817             SbiRuntime* pRT = pInst->pRun;
818             bTextMode = pRT && pRT->IsImageFlag( SbiImageFlags::COMPARETEXT );
819         }
820         else
821         {
822             bTextMode = true;
823         }
824         if ( nArgCount == 4 )
825         {
826             bTextMode = rPar.Get(4)->GetInteger();
827         }
828         sal_Int32 nPos;
829         const OUString& rToken = rPar.Get(nFirstStringPos + 1)->GetOUString();
830 
831         // #97545 Always find empty string
832         if( rToken.isEmpty() )
833         {
834             nPos = nStartPos;
835         }
836         else
837         {
838             const OUString& rStr1 = rPar.Get(nFirstStringPos)->GetOUString();
839             const sal_Int32 nrStr1Len = rStr1.getLength();
840             if (nStartPos > nrStr1Len)
841             {
842                 // Start position is greater than the string being searched
843                 nPos = 0;
844             }
845             else
846             {
847                 if( !bTextMode )
848                 {
849                     nPos = rStr1.indexOf( rToken, nStartPos - 1 ) + 1;
850                 }
851                 else
852                 {
853                     // tdf#139840 - case-insensitive operation for non-ASCII characters
854                     i18nutil::SearchOptions2 aSearchOptions;
855                     aSearchOptions.searchString = rToken;
856                     aSearchOptions.AlgorithmType2 = util::SearchAlgorithms2::ABSOLUTE;
857                     aSearchOptions.transliterateFlags |= TransliterationFlags::IGNORE_CASE;
858                     utl::TextSearch textSearch(aSearchOptions);
859 
860                     sal_Int32 nStart = nStartPos - 1;
861                     sal_Int32 nEnd = nrStr1Len;
862                     nPos = textSearch.SearchForward(rStr1, &nStart, &nEnd) ? nStart + 1 : 0;
863                 }
864             }
865         }
866         rPar.Get(0)->PutLong(nPos);
867     }
868 }
869 
870 
871 // InstrRev(string1, string2[, start[, compare]])
872 
873 void SbRtl_InStrRev(StarBASIC *, SbxArray & rPar, bool)
874 {
875     const sal_uInt32 nArgCount = rPar.Count() - 1;
876     if ( nArgCount < 2 )
877     {
878         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
879     }
880 
881     const OUString aStr1 = rPar.Get(1)->GetOUString();
882     const OUString aToken = rPar.Get(2)->GetOUString();
883 
884     sal_Int32 nStartPos = -1;
885     if ( nArgCount >= 3 )
886     {
887         nStartPos = rPar.Get(3)->GetLong();
888         if( nStartPos <= 0 && nStartPos != -1 )
889         {
890             StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
891             nStartPos = -1;
892         }
893     }
894 
895     SbiInstance* pInst = GetSbData()->pInst;
896     bool bTextMode;
897     bool bCompatibility = ( pInst && pInst->IsCompatibility() );
898     if( bCompatibility )
899     {
900         SbiRuntime* pRT = pInst->pRun;
901         bTextMode = pRT && pRT->IsImageFlag( SbiImageFlags::COMPARETEXT );
902     }
903     else
904     {
905         bTextMode = true;
906     }
907     if ( nArgCount == 4 )
908     {
909         bTextMode = rPar.Get(4)->GetInteger();
910     }
911     const sal_Int32 nStrLen = aStr1.getLength();
912     if( nStartPos == -1 )
913     {
914         nStartPos = nStrLen;
915     }
916 
917     sal_Int32 nPos = 0;
918     if( nStartPos <= nStrLen )
919     {
920         sal_Int32 nTokenLen = aToken.getLength();
921         if( !nTokenLen )
922         {
923            // Always find empty string
924            nPos = nStartPos;
925         }
926         else if( nStrLen > 0 )
927         {
928             if( !bTextMode )
929             {
930                nPos = aStr1.lastIndexOf( aToken, nStartPos ) + 1;
931             }
932             else
933             {
934                // tdf#143332 - case-insensitive operation for non-ASCII characters
935                i18nutil::SearchOptions2 aSearchOptions;
936                aSearchOptions.searchString = aToken;
937                aSearchOptions.AlgorithmType2 = util::SearchAlgorithms2::ABSOLUTE;
938                aSearchOptions.transliterateFlags |= TransliterationFlags::IGNORE_CASE;
939                utl::TextSearch textSearch(aSearchOptions);
940 
941                sal_Int32 nStart = 0;
942                sal_Int32 nEnd = nStartPos;
943                nPos = textSearch.SearchBackward(aStr1, &nEnd, &nStart) ? nStart : 0;
944             }
945         }
946     }
947     rPar.Get(0)->PutLong(nPos);
948 }
949 
950 
951 /*
952     Int( 2.8 )  =  2.0
953     Int( -2.8 ) = -3.0
954     Fix( 2.8 )  =  2.0
955     Fix( -2.8 ) = -2.0    <- !!
956 */
957 
958 void SbRtl_Int(StarBASIC *, SbxArray & rPar, bool)
959 {
960     if (rPar.Count() < 2)
961         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
962 
963     SbxVariableRef pArg = rPar.Get(1);
964     double aDouble= pArg->GetDouble();
965     /*
966         floor( 2.8 ) =  2.0
967         floor( -2.8 ) = -3.0
968     */
969     aDouble = floor( aDouble );
970     rPar.Get(0)->PutDouble(aDouble);
971 }
972 
973 
974 void SbRtl_Fix(StarBASIC *, SbxArray & rPar, bool)
975 {
976     if (rPar.Count() < 2)
977         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
978 
979     SbxVariableRef pArg = rPar.Get(1);
980     double aDouble = pArg->GetDouble();
981     if ( aDouble >= 0.0 )
982         aDouble = floor( aDouble );
983     else
984         aDouble = ceil( aDouble );
985     rPar.Get(0)->PutDouble(aDouble);
986 }
987 
988 
989 void SbRtl_LCase(StarBASIC *, SbxArray & rPar, bool)
990 {
991     if (rPar.Count() < 2)
992         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
993 
994     const CharClass& rCharClass = GetCharClass();
995     OUString aStr(rPar.Get(1)->GetOUString());
996     aStr = rCharClass.lowercase(aStr);
997     rPar.Get(0)->PutString(aStr);
998 }
999 
1000 void SbRtl_Left(StarBASIC *, SbxArray & rPar, bool)
1001 {
1002     if (rPar.Count() < 3)
1003     {
1004         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1005     }
1006     else
1007     {
1008         OUString aStr(rPar.Get(1)->GetOUString());
1009         sal_Int32 nResultLen = rPar.Get(2)->GetLong();
1010         if( nResultLen < 0 )
1011         {
1012             nResultLen = 0;
1013             StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1014         }
1015         else if(nResultLen > aStr.getLength())
1016         {
1017             nResultLen = aStr.getLength();
1018         }
1019         aStr = aStr.copy(0, nResultLen );
1020         rPar.Get(0)->PutString(aStr);
1021     }
1022 }
1023 
1024 void SbRtl_Log(StarBASIC *, SbxArray & rPar, bool)
1025 {
1026     if (rPar.Count() < 2)
1027     {
1028         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1029     }
1030     else
1031     {
1032         double aArg = rPar.Get(1)->GetDouble();
1033         if ( aArg > 0 )
1034         {
1035             double d = log( aArg );
1036             checkArithmeticOverflow( d );
1037             rPar.Get(0)->PutDouble(d);
1038         }
1039         else
1040         {
1041             StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1042         }
1043     }
1044 }
1045 
1046 void SbRtl_LTrim(StarBASIC *, SbxArray & rPar, bool)
1047 {
1048     if (rPar.Count() < 2)
1049         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1050 
1051     OUString aStr(comphelper::string::stripStart(rPar.Get(1)->GetOUString(), ' '));
1052     rPar.Get(0)->PutString(aStr);
1053 }
1054 
1055 
1056 // Mid( String, nStart, nLength )
1057 
1058 void SbRtl_Mid(StarBASIC *, SbxArray & rPar, bool bWrite)
1059 {
1060     int nArgCount = rPar.Count() - 1;
1061     if ( nArgCount < 2 )
1062     {
1063         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1064     }
1065     else
1066     {
1067         // #23178: replicate the functionality of Mid$ as a command
1068         // by adding a replacement-string as a fourth parameter.
1069         // In contrast to the original the third parameter (nLength)
1070         // can't be left out here. That's considered in bWrite already.
1071         if( nArgCount == 4 )
1072         {
1073             bWrite = true;
1074         }
1075         OUString aArgStr = rPar.Get(1)->GetOUString();
1076         sal_Int32 nStartPos = rPar.Get(2)->GetLong();
1077         if ( nStartPos < 1 )
1078         {
1079             StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1080         }
1081         else
1082         {
1083             nStartPos--;
1084             sal_Int32 nLen = -1;
1085             bool bWriteNoLenParam = false;
1086             if ( nArgCount == 3 || bWrite )
1087             {
1088                 sal_Int32 n = rPar.Get(3)->GetLong();
1089                 if( bWrite && n == -1 )
1090                 {
1091                     bWriteNoLenParam = true;
1092                 }
1093                 nLen = n;
1094             }
1095             if ( bWrite )
1096             {
1097                 sal_Int32 nArgLen = aArgStr.getLength();
1098                 if( nStartPos > nArgLen )
1099                 {
1100                     SbiInstance* pInst = GetSbData()->pInst;
1101                     bool bCompatibility = ( pInst && pInst->IsCompatibility() );
1102                     if( bCompatibility )
1103                     {
1104                         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1105                     }
1106                     nStartPos = nArgLen;
1107                 }
1108 
1109                 OUString aReplaceStr = rPar.Get(4)->GetOUString();
1110                 sal_Int32 nReplaceStrLen = aReplaceStr.getLength();
1111                 sal_Int32 nReplaceLen;
1112                 if( bWriteNoLenParam )
1113                 {
1114                     nReplaceLen = nArgLen - nStartPos;
1115                 }
1116                 else
1117                 {
1118                     nReplaceLen = nLen;
1119                     if( nReplaceLen < 0 || nReplaceLen > nArgLen - nStartPos )
1120                     {
1121                         nReplaceLen = nArgLen - nStartPos;
1122                     }
1123                 }
1124 
1125                 OUStringBuffer aResultStr(aArgStr);
1126                 sal_Int32 nErase = nReplaceLen;
1127                 aResultStr.remove( nStartPos, nErase );
1128                 aResultStr.insert(
1129                     nStartPos, aReplaceStr.getStr(), std::min(nReplaceLen, nReplaceStrLen));
1130 
1131                 rPar.Get(1)->PutString(aResultStr.makeStringAndClear());
1132             }
1133             else
1134             {
1135                 OUString aResultStr;
1136                 if (nStartPos > aArgStr.getLength())
1137                 {
1138                     // do nothing
1139                 }
1140                 else if(nArgCount == 2)
1141                 {
1142                     aResultStr = aArgStr.copy( nStartPos);
1143                 }
1144                 else
1145                 {
1146                     if (nLen < 0)
1147                         nLen = 0;
1148                     if(nStartPos + nLen > aArgStr.getLength())
1149                     {
1150                         nLen = aArgStr.getLength() - nStartPos;
1151                     }
1152                     if (nLen > 0)
1153                         aResultStr = aArgStr.copy( nStartPos, nLen );
1154                 }
1155                 rPar.Get(0)->PutString(aResultStr);
1156             }
1157         }
1158     }
1159 }
1160 
1161 void SbRtl_Oct(StarBASIC *, SbxArray & rPar, bool)
1162 {
1163     if (rPar.Count() < 2)
1164     {
1165         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1166     }
1167     else
1168     {
1169         SbxVariableRef pArg = rPar.Get(1);
1170         // converting value to unsigned and limit to 2 or 4 byte representation
1171         sal_uInt32 nVal = pArg->IsInteger() ?
1172             static_cast<sal_uInt16>(pArg->GetInteger()) :
1173             static_cast<sal_uInt32>(pArg->GetLong());
1174         rPar.Get(0)->PutString(OUString::number(nVal, 8));
1175     }
1176 }
1177 
1178 // Replace(expression, find, replace[, start[, count[, compare]]])
1179 
1180 void SbRtl_Replace(StarBASIC *, SbxArray & rPar, bool)
1181 {
1182     const sal_uInt32 nArgCount = rPar.Count() - 1;
1183     if ( nArgCount < 3 || nArgCount > 6 )
1184     {
1185         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1186     }
1187 
1188     sal_Int32 lStartPos = 1;
1189     if (nArgCount >= 4)
1190     {
1191         if (rPar.Get(4)->GetType() != SbxEMPTY)
1192         {
1193             lStartPos = rPar.Get(4)->GetLong();
1194         }
1195         if (lStartPos < 1)
1196         {
1197             return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
1198         }
1199     }
1200     --lStartPos; // Make it 0-based
1201 
1202     sal_Int32 lCount = -1;
1203     if (nArgCount >= 5)
1204     {
1205         if (rPar.Get(5)->GetType() != SbxEMPTY)
1206         {
1207             lCount = rPar.Get(5)->GetLong();
1208         }
1209         if (lCount < -1)
1210         {
1211             return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
1212         }
1213     }
1214 
1215     bool bCaseInsensitive;
1216     if (nArgCount == 6)
1217     {
1218         bCaseInsensitive = rPar.Get(6)->GetInteger();
1219     }
1220     else
1221     {
1222         SbiInstance* pInst = GetSbData()->pInst;
1223         if (pInst && pInst->IsCompatibility())
1224         {
1225             SbiRuntime* pRT = pInst->pRun;
1226             bCaseInsensitive = pRT && pRT->IsImageFlag(SbiImageFlags::COMPARETEXT);
1227         }
1228         else
1229         {
1230             bCaseInsensitive = true;
1231         }
1232     }
1233 
1234     const OUString aExpStr = rPar.Get(1)->GetOUString();
1235     OUString aFindStr = rPar.Get(2)->GetOUString();
1236     const OUString aReplaceStr = rPar.Get(3)->GetOUString();
1237 
1238     OUString aSrcStr(aExpStr);
1239     sal_Int32 nPrevPos = std::min(lStartPos, aSrcStr.getLength());
1240     css::uno::Sequence<sal_Int32> aOffset;
1241     if (bCaseInsensitive)
1242     {
1243         // tdf#132389: case-insensitive operation for non-ASCII characters
1244         // tdf#142487: use css::i18n::Transliteration to correctly handle ß -> ss expansion
1245         // tdf#132388: We can't use utl::TextSearch (css::i18n::XTextSearch), because each call to
1246         //             css::i18n::XTextSearch::SearchForward transliterates input string, making
1247         //             performance of repeated calls unacceptable
1248         auto xTrans = css::i18n::Transliteration::create(comphelper::getProcessComponentContext());
1249         xTrans->loadModule(css::i18n::TransliterationModules_IGNORE_CASE, {});
1250         aFindStr = xTrans->transliterate(aFindStr, 0, aFindStr.getLength(), aOffset);
1251         aSrcStr = xTrans->transliterate(aSrcStr, nPrevPos, aSrcStr.getLength() - nPrevPos, aOffset);
1252         nPrevPos = std::distance(aOffset.begin(),
1253                                  std::lower_bound(aOffset.begin(), aOffset.end(), nPrevPos));
1254     }
1255 
1256     auto getExpStrPos = [aOffset, nExpLen = aExpStr.getLength()](sal_Int32 nSrcStrPos) -> sal_Int32
1257     {
1258         assert(!aOffset.hasElements() || aOffset.getLength() >= nSrcStrPos);
1259         if (!aOffset.hasElements())
1260             return nSrcStrPos;
1261         return aOffset.getLength() > nSrcStrPos ? aOffset[nSrcStrPos] : nExpLen;
1262     };
1263 
1264     // Note: the result starts from lStartPos, removing everything to the left. See i#94895.
1265     OUStringBuffer sResult(aSrcStr.getLength() - nPrevPos);
1266     sal_Int32 nCounts = 0;
1267     while (lCount == -1 || lCount > nCounts)
1268     {
1269         sal_Int32 nPos = aSrcStr.indexOf(aFindStr, nPrevPos);
1270         if (nPos < 0)
1271             break;
1272 
1273         lStartPos = getExpStrPos(nPrevPos);
1274         sResult.append(aExpStr.getStr() + lStartPos, getExpStrPos(nPos) - lStartPos);
1275         sResult.append(aReplaceStr);
1276         nPrevPos = nPos + aFindStr.getLength();
1277         nCounts++;
1278     }
1279     lStartPos = getExpStrPos(nPrevPos);
1280     sResult.append(aExpStr.getStr() + lStartPos, aExpStr.getLength() - lStartPos);
1281     rPar.Get(0)->PutString(sResult.makeStringAndClear());
1282 }
1283 
1284 void SbRtl_Right(StarBASIC *, SbxArray & rPar, bool)
1285 {
1286     if (rPar.Count() < 3)
1287     {
1288         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1289     }
1290     else
1291     {
1292         const OUString& rStr = rPar.Get(1)->GetOUString();
1293         int nResultLen = rPar.Get(2)->GetLong();
1294         if( nResultLen < 0 )
1295         {
1296             nResultLen = 0;
1297             StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1298         }
1299         int nStrLen = rStr.getLength();
1300         if ( nResultLen > nStrLen )
1301         {
1302             nResultLen = nStrLen;
1303         }
1304         OUString aResultStr = rStr.copy( nStrLen - nResultLen );
1305         rPar.Get(0)->PutString(aResultStr);
1306     }
1307 }
1308 
1309 void SbRtl_RTL(StarBASIC * pBasic, SbxArray & rPar, bool)
1310 {
1311     rPar.Get(0)->PutObject(pBasic->getRTL().get());
1312 }
1313 
1314 void SbRtl_RTrim(StarBASIC *, SbxArray & rPar, bool)
1315 {
1316     if (rPar.Count() < 2)
1317         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1318 
1319     OUString aStr(comphelper::string::stripEnd(rPar.Get(1)->GetOUString(), ' '));
1320     rPar.Get(0)->PutString(aStr);
1321 }
1322 
1323 void SbRtl_Sgn(StarBASIC *, SbxArray & rPar, bool)
1324 {
1325     if (rPar.Count() < 2)
1326     {
1327         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1328     }
1329     else
1330     {
1331         double aDouble = rPar.Get(1)->GetDouble();
1332         sal_Int16 nResult = 0;
1333         if ( aDouble > 0 )
1334         {
1335             nResult = 1;
1336         }
1337         else if ( aDouble < 0 )
1338         {
1339             nResult = -1;
1340         }
1341         rPar.Get(0)->PutInteger(nResult);
1342     }
1343 }
1344 
1345 void SbRtl_Space(StarBASIC *, SbxArray & rPar, bool)
1346 {
1347     if (rPar.Count() < 2)
1348     {
1349         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1350     }
1351     else
1352     {
1353         const sal_Int32 nCount = rPar.Get(1)->GetLong();
1354         OUStringBuffer aBuf(nCount);
1355         string::padToLength(aBuf, nCount, ' ');
1356         rPar.Get(0)->PutString(aBuf.makeStringAndClear());
1357     }
1358 }
1359 
1360 void SbRtl_Sqr(StarBASIC *, SbxArray & rPar, bool)
1361 {
1362     if (rPar.Count() < 2)
1363     {
1364         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1365     }
1366     else
1367     {
1368         double aDouble = rPar.Get(1)->GetDouble();
1369         if ( aDouble >= 0 )
1370         {
1371             rPar.Get(0)->PutDouble(sqrt(aDouble));
1372         }
1373         else
1374         {
1375             StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1376         }
1377     }
1378 }
1379 
1380 void SbRtl_Str(StarBASIC *, SbxArray & rPar, bool)
1381 {
1382     if (rPar.Count() < 2)
1383     {
1384         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1385     }
1386     else
1387     {
1388         OUString aStr;
1389         OUString aStrNew("");
1390         SbxVariableRef pArg = rPar.Get(1);
1391         pArg->Format( aStr );
1392 
1393         // Numbers start with a space
1394         if( pArg->IsNumericRTL() )
1395         {
1396             // replace commas by points so that it's symmetric to Val!
1397             aStr = aStr.replaceFirst( ",", "." );
1398 
1399             SbiInstance* pInst = GetSbData()->pInst;
1400             bool bCompatibility = ( pInst && pInst->IsCompatibility() );
1401             if( bCompatibility )
1402             {
1403                 sal_Int32 nLen = aStr.getLength();
1404 
1405                 const sal_Unicode* pBuf = aStr.getStr();
1406 
1407                 bool bNeg = ( pBuf[0] == '-' );
1408                 sal_Int32 iZeroSearch = 0;
1409                 if( bNeg )
1410                 {
1411                     aStrNew += "-";
1412                     iZeroSearch++;
1413                 }
1414                 else
1415                 {
1416                     if( pBuf[0] != ' ' )
1417                     {
1418                         aStrNew += " ";
1419                     }
1420                 }
1421                 sal_Int32 iNext = iZeroSearch + 1;
1422                 if( pBuf[iZeroSearch] == '0' && nLen > iNext && pBuf[iNext] == '.' )
1423                 {
1424                     iZeroSearch += 1;
1425                 }
1426                 aStrNew += aStr.subView(iZeroSearch);
1427             }
1428             else
1429             {
1430                 aStrNew = " " + aStr;
1431             }
1432         }
1433         else
1434         {
1435             aStrNew = aStr;
1436         }
1437         rPar.Get(0)->PutString(aStrNew);
1438     }
1439 }
1440 
1441 void SbRtl_StrComp(StarBASIC *, SbxArray & rPar, bool)
1442 {
1443     if (rPar.Count() < 3)
1444     {
1445         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1446         rPar.Get(0)->PutEmpty();
1447         return;
1448     }
1449     const OUString& rStr1 = rPar.Get(1)->GetOUString();
1450     const OUString& rStr2 = rPar.Get(2)->GetOUString();
1451 
1452     SbiInstance* pInst = GetSbData()->pInst;
1453     bool bTextCompare;
1454     bool bCompatibility = ( pInst && pInst->IsCompatibility() );
1455     if( bCompatibility )
1456     {
1457         SbiRuntime* pRT = pInst->pRun;
1458         bTextCompare = pRT && pRT->IsImageFlag( SbiImageFlags::COMPARETEXT );
1459     }
1460     else
1461     {
1462         bTextCompare = true;
1463     }
1464     if (rPar.Count() == 4)
1465         bTextCompare = rPar.Get(3)->GetInteger();
1466 
1467     if( !bCompatibility )
1468     {
1469         bTextCompare = !bTextCompare;
1470     }
1471     sal_Int32 nRetValue = 0;
1472     if( bTextCompare )
1473     {
1474         ::utl::TransliterationWrapper* pTransliterationWrapper = GetSbData()->pTransliterationWrapper.get();
1475         if( !pTransliterationWrapper )
1476         {
1477             uno::Reference< uno::XComponentContext > xContext = getProcessComponentContext();
1478             GetSbData()->pTransliterationWrapper.reset(
1479                 new ::utl::TransliterationWrapper( xContext,
1480                     TransliterationFlags::IGNORE_CASE |
1481                     TransliterationFlags::IGNORE_KANA |
1482                     TransliterationFlags::IGNORE_WIDTH ) );
1483             pTransliterationWrapper = GetSbData()->pTransliterationWrapper.get();
1484         }
1485 
1486         LanguageType eLangType = Application::GetSettings().GetLanguageTag().getLanguageType();
1487         pTransliterationWrapper->loadModuleIfNeeded( eLangType );
1488         nRetValue = pTransliterationWrapper->compareString( rStr1, rStr2 );
1489     }
1490     else
1491     {
1492         sal_Int32 aResult;
1493         aResult = rStr1.compareTo( rStr2 );
1494         if ( aResult < 0  )
1495         {
1496             nRetValue = -1;
1497         }
1498         else if ( aResult > 0)
1499         {
1500             nRetValue = 1;
1501         }
1502     }
1503     rPar.Get(0)->PutInteger(sal::static_int_cast<sal_Int16>(nRetValue));
1504 }
1505 
1506 void SbRtl_String(StarBASIC *, SbxArray & rPar, bool)
1507 {
1508     if (rPar.Count() < 2)
1509     {
1510         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1511     }
1512     else
1513     {
1514         sal_Unicode aFiller;
1515         sal_Int32 lCount = rPar.Get(1)->GetLong();
1516         if( lCount < 0 || lCount > 0xffff )
1517         {
1518             StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1519         }
1520         if (rPar.Get(2)->GetType() == SbxINTEGER)
1521         {
1522             aFiller = static_cast<sal_Unicode>(rPar.Get(2)->GetInteger());
1523         }
1524         else
1525         {
1526             const OUString& rStr = rPar.Get(2)->GetOUString();
1527             aFiller = rStr[0];
1528         }
1529         OUStringBuffer aBuf(lCount);
1530         string::padToLength(aBuf, lCount, aFiller);
1531         rPar.Get(0)->PutString(aBuf.makeStringAndClear());
1532     }
1533 }
1534 
1535 void SbRtl_Tab(StarBASIC *, SbxArray & rPar, bool)
1536 {
1537     if (rPar.Count() < 2)
1538         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1539     else
1540     {
1541         const sal_Int32 nCount = std::max(rPar.Get(1)->GetLong(), sal_Int32(0));
1542         OUStringBuffer aStr(nCount);
1543         comphelper::string::padToLength(aStr, nCount, '\t');
1544         rPar.Get(0)->PutString(aStr.makeStringAndClear());
1545     }
1546 }
1547 
1548 void SbRtl_Tan(StarBASIC *, SbxArray & rPar, bool)
1549 {
1550     if (rPar.Count() < 2)
1551     {
1552         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1553     }
1554     else
1555     {
1556         SbxVariableRef pArg = rPar.Get(1);
1557         rPar.Get(0)->PutDouble(tan(pArg->GetDouble()));
1558     }
1559 }
1560 
1561 void SbRtl_UCase(StarBASIC *, SbxArray & rPar, bool)
1562 {
1563     if (rPar.Count() < 2)
1564     {
1565         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1566     }
1567     else
1568     {
1569         const CharClass& rCharClass = GetCharClass();
1570         OUString aStr(rPar.Get(1)->GetOUString());
1571         aStr = rCharClass.uppercase( aStr );
1572         rPar.Get(0)->PutString(aStr);
1573     }
1574 }
1575 
1576 
1577 void SbRtl_Val(StarBASIC *, SbxArray & rPar, bool)
1578 {
1579     if (rPar.Count() < 2)
1580     {
1581         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1582     }
1583     else
1584     {
1585         double nResult = 0.0;
1586         char* pEndPtr;
1587 
1588         OUString aStr(rPar.Get(1)->GetOUString());
1589 
1590         FilterWhiteSpace( aStr );
1591         if ( aStr.getLength() > 1 && aStr[0] == '&' )
1592         {
1593             int nRadix = 10;
1594             char aChar = static_cast<char>(aStr[1]);
1595             if ( aChar == 'h' || aChar == 'H' )
1596             {
1597                 nRadix = 16;
1598             }
1599             else if ( aChar == 'o' || aChar == 'O' )
1600             {
1601                 nRadix = 8;
1602             }
1603             if ( nRadix != 10 )
1604             {
1605                 OString aByteStr(OUStringToOString(aStr, osl_getThreadTextEncoding()));
1606                 sal_Int16 nlResult = static_cast<sal_Int16>(strtol( aByteStr.getStr()+2, &pEndPtr, nRadix));
1607                 nResult = static_cast<double>(nlResult);
1608             }
1609         }
1610         else
1611         {
1612             rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
1613             sal_Int32 nParseEnd = 0;
1614             nResult = ::rtl::math::stringToDouble( aStr, '.', ',', &eStatus, &nParseEnd );
1615             if ( eStatus != rtl_math_ConversionStatus_Ok )
1616                 StarBASIC::Error( ERRCODE_BASIC_MATH_OVERFLOW );
1617             /* TODO: we should check whether all characters were parsed here,
1618              * but earlier code silently ignored trailing nonsense such as "1x"
1619              * resulting in 1 with the side effect that any alpha-only-string
1620              * like "x" resulted in 0. Not changing that now (2013-03-22) as
1621              * user macros may rely on it. */
1622 #if 0
1623             else if ( nParseEnd != aStr.getLength() )
1624                 StarBASIC::Error( ERRCODE_BASIC_CONVERSION );
1625 #endif
1626         }
1627 
1628         rPar.Get(0)->PutDouble(nResult);
1629     }
1630 }
1631 
1632 
1633 // Helper functions for date conversion
1634 sal_Int16 implGetDateDay( double aDate )
1635 {
1636     aDate = floor( aDate );
1637     Date aRefDate(1899'12'30);
1638     aRefDate.AddDays( aDate );
1639 
1640     sal_Int16 nRet = static_cast<sal_Int16>( aRefDate.GetDay() );
1641     return nRet;
1642 }
1643 
1644 sal_Int16 implGetDateMonth( double aDate )
1645 {
1646     Date aRefDate(1899'12'30);
1647     sal_Int32 nDays = static_cast<sal_Int32>(aDate);
1648     aRefDate.AddDays( nDays );
1649     sal_Int16 nRet = static_cast<sal_Int16>( aRefDate.GetMonth() );
1650     return nRet;
1651 }
1652 
1653 css::util::Date SbxDateToUNODate( const SbxValue* const pVal )
1654 {
1655     double aDate = pVal->GetDate();
1656 
1657     css::util::Date aUnoDate;
1658     aUnoDate.Day   = implGetDateDay  ( aDate );
1659     aUnoDate.Month = implGetDateMonth( aDate );
1660     aUnoDate.Year  = implGetDateYear ( aDate );
1661 
1662     return aUnoDate;
1663 }
1664 
1665 void SbxDateFromUNODate( SbxValue *pVal, const css::util::Date& aUnoDate)
1666 {
1667     double dDate;
1668     if( implDateSerial( aUnoDate.Year, aUnoDate.Month, aUnoDate.Day, false, SbDateCorrection::None, dDate ) )
1669     {
1670         pVal->PutDate( dDate );
1671     }
1672 }
1673 
1674 // Function to convert date to UNO date (com.sun.star.util.Date)
1675 void SbRtl_CDateToUnoDate(StarBASIC *, SbxArray & rPar, bool)
1676 {
1677     if (rPar.Count() != 2)
1678     {
1679         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1680     }
1681 
1682     unoToSbxValue(rPar.Get(0), Any(SbxDateToUNODate(rPar.Get(1))));
1683 }
1684 
1685 // Function to convert date from UNO date (com.sun.star.util.Date)
1686 void SbRtl_CDateFromUnoDate(StarBASIC *, SbxArray & rPar, bool)
1687 {
1688     if (rPar.Count() != 2 || rPar.Get(1)->GetType() != SbxOBJECT)
1689     {
1690         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1691     }
1692 
1693     Any aAny(sbxToUnoValue(rPar.Get(1), cppu::UnoType<css::util::Date>::get()));
1694     css::util::Date aUnoDate;
1695     if(aAny >>= aUnoDate)
1696         SbxDateFromUNODate(rPar.Get(0), aUnoDate);
1697     else
1698         SbxBase::SetError( ERRCODE_BASIC_CONVERSION );
1699 }
1700 
1701 css::util::Time SbxDateToUNOTime( const SbxValue* const pVal )
1702 {
1703     double aDate = pVal->GetDate();
1704 
1705     css::util::Time aUnoTime;
1706     aUnoTime.Hours       = implGetHour      ( aDate );
1707     aUnoTime.Minutes     = implGetMinute    ( aDate );
1708     aUnoTime.Seconds     = implGetSecond    ( aDate );
1709     aUnoTime.NanoSeconds = 0;
1710 
1711     return aUnoTime;
1712 }
1713 
1714 void SbxDateFromUNOTime( SbxValue *pVal, const css::util::Time& aUnoTime)
1715 {
1716     pVal->PutDate( implTimeSerial(aUnoTime.Hours, aUnoTime.Minutes, aUnoTime.Seconds) );
1717 }
1718 
1719 // Function to convert date to UNO time (com.sun.star.util.Time)
1720 void SbRtl_CDateToUnoTime(StarBASIC *, SbxArray & rPar, bool)
1721 {
1722     if (rPar.Count() != 2)
1723     {
1724         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1725     }
1726 
1727     unoToSbxValue(rPar.Get(0), Any(SbxDateToUNOTime(rPar.Get(1))));
1728 }
1729 
1730 // Function to convert date from UNO time (com.sun.star.util.Time)
1731 void SbRtl_CDateFromUnoTime(StarBASIC *, SbxArray & rPar, bool)
1732 {
1733     if (rPar.Count() != 2 || rPar.Get(1)->GetType() != SbxOBJECT)
1734     {
1735         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1736     }
1737 
1738     Any aAny(sbxToUnoValue(rPar.Get(1), cppu::UnoType<css::util::Time>::get()));
1739     css::util::Time aUnoTime;
1740     if(aAny >>= aUnoTime)
1741         SbxDateFromUNOTime(rPar.Get(0), aUnoTime);
1742     else
1743         SbxBase::SetError( ERRCODE_BASIC_CONVERSION );
1744 }
1745 
1746 css::util::DateTime SbxDateToUNODateTime( const SbxValue* const pVal )
1747 {
1748     double aDate = pVal->GetDate();
1749 
1750     css::util::DateTime aUnoDT;
1751     aUnoDT.Day   = implGetDateDay  ( aDate );
1752     aUnoDT.Month = implGetDateMonth( aDate );
1753     aUnoDT.Year  = implGetDateYear ( aDate );
1754     aUnoDT.Hours       = implGetHour      ( aDate );
1755     aUnoDT.Minutes     = implGetMinute    ( aDate );
1756     aUnoDT.Seconds     = implGetSecond    ( aDate );
1757     aUnoDT.NanoSeconds = 0;
1758 
1759     return aUnoDT;
1760 }
1761 
1762 void SbxDateFromUNODateTime( SbxValue *pVal, const css::util::DateTime& aUnoDT)
1763 {
1764     double dDate(0.0);
1765     if( implDateTimeSerial( aUnoDT.Year, aUnoDT.Month, aUnoDT.Day,
1766                             aUnoDT.Hours, aUnoDT.Minutes, aUnoDT.Seconds,
1767                             dDate ) )
1768     {
1769         pVal->PutDate( dDate );
1770     }
1771 }
1772 
1773 // Function to convert date to UNO date (com.sun.star.util.Date)
1774 void SbRtl_CDateToUnoDateTime(StarBASIC *, SbxArray & rPar, bool)
1775 {
1776     if (rPar.Count() != 2)
1777     {
1778         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1779     }
1780 
1781     unoToSbxValue(rPar.Get(0), Any(SbxDateToUNODateTime(rPar.Get(1))));
1782 }
1783 
1784 // Function to convert date from UNO date (com.sun.star.util.Date)
1785 void SbRtl_CDateFromUnoDateTime(StarBASIC *, SbxArray & rPar, bool)
1786 {
1787     if (rPar.Count() != 2 || rPar.Get(1)->GetType() != SbxOBJECT)
1788     {
1789         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1790     }
1791 
1792     Any aAny(sbxToUnoValue(rPar.Get(1), cppu::UnoType<css::util::DateTime>::get()));
1793     css::util::DateTime aUnoDT;
1794     if(aAny >>= aUnoDT)
1795         SbxDateFromUNODateTime(rPar.Get(0), aUnoDT);
1796     else
1797         SbxBase::SetError( ERRCODE_BASIC_CONVERSION );
1798 }
1799 
1800 // Function to convert date to ISO 8601 date format YYYYMMDD
1801 void SbRtl_CDateToIso(StarBASIC *, SbxArray & rPar, bool)
1802 {
1803     if (rPar.Count() == 2)
1804     {
1805         double aDate = rPar.Get(1)->GetDate();
1806 
1807         // Date may actually even be -YYYYYMMDD
1808         char Buffer[11];
1809         sal_Int16 nYear = implGetDateYear( aDate );
1810         snprintf( Buffer, sizeof( Buffer ), (nYear < 0 ? "%05d%02d%02d" : "%04d%02d%02d"),
1811                 static_cast<int>(nYear),
1812                 static_cast<int>(implGetDateMonth( aDate )),
1813                 static_cast<int>(implGetDateDay( aDate )) );
1814         OUString aRetStr = OUString::createFromAscii( Buffer );
1815         rPar.Get(0)->PutString(aRetStr);
1816     }
1817     else
1818     {
1819         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1820     }
1821 }
1822 
1823 // Function to convert date from ISO 8601 date format YYYYMMDD or YYYY-MM-DD
1824 // And even YYMMDD for compatibility, sigh...
1825 void SbRtl_CDateFromIso(StarBASIC *, SbxArray & rPar, bool)
1826 {
1827     if (rPar.Count() == 2)
1828     {
1829         do
1830         {
1831             OUString aStr = rPar.Get(1)->GetOUString();
1832             if (aStr.isEmpty())
1833                 break;
1834 
1835             // Valid formats are
1836             // YYYYMMDD    -YYYMMDD     YYYYYMMDD    -YYYYYMMDD    YYMMDD
1837             // YYYY-MM-DD  -YYYY-MM-DD  YYYYY-MM-DD  -YYYYY-MM-DD
1838 
1839             sal_Int32 nSign = 1;
1840             if (aStr[0] == '-')
1841             {
1842                 nSign = -1;
1843                 aStr = aStr.copy(1);
1844             }
1845             const sal_Int32 nLen = aStr.getLength();
1846 
1847             // Signed YYMMDD two digit year is invalid.
1848             if (nLen == 6 && nSign == -1)
1849                 break;
1850 
1851             // Now valid
1852             // YYYYMMDD    YYYYYMMDD    YYMMDD
1853             // YYYY-MM-DD  YYYYY-MM-DD
1854             if (nLen != 6 && (nLen < 8 || 11 < nLen))
1855                 break;
1856 
1857             bool bUseTwoDigitYear = false;
1858             std::u16string_view aYearStr, aMonthStr, aDayStr;
1859             if (nLen == 6 || nLen == 8 || nLen == 9)
1860             {
1861                 // ((Y)YY)YYMMDD
1862                 if (!comphelper::string::isdigitAsciiString(aStr))
1863                     break;
1864 
1865                 const sal_Int32 nMonthPos = (nLen == 8 ? 4 : (nLen == 6 ? 2 : 5));
1866                 if (nMonthPos == 2)
1867                     bUseTwoDigitYear = true;
1868                 aYearStr  = aStr.subView( 0, nMonthPos );
1869                 aMonthStr = aStr.subView( nMonthPos, 2 );
1870                 aDayStr   = aStr.subView( nMonthPos + 2, 2 );
1871             }
1872             else
1873             {
1874                 // (Y)YYYY-MM-DD
1875                 const sal_Int32 nMonthSep = (nLen == 11 ? 5 : 4);
1876                 if (aStr.indexOf('-') != nMonthSep)
1877                     break;
1878                 if (aStr.indexOf('-', nMonthSep + 1) != nMonthSep + 3)
1879                     break;
1880 
1881                 aYearStr  = aStr.subView( 0, nMonthSep );
1882                 aMonthStr = aStr.subView( nMonthSep + 1, 2 );
1883                 aDayStr   = aStr.subView( nMonthSep + 4, 2 );
1884                 if (    !comphelper::string::isdigitAsciiString(aYearStr) ||
1885                         !comphelper::string::isdigitAsciiString(aMonthStr) ||
1886                         !comphelper::string::isdigitAsciiString(aDayStr))
1887                     break;
1888             }
1889 
1890             double dDate;
1891             if (!implDateSerial( static_cast<sal_Int16>(nSign * o3tl::toInt32(aYearStr)),
1892                         static_cast<sal_Int16>(o3tl::toInt32(aMonthStr)), static_cast<sal_Int16>(o3tl::toInt32(aDayStr)),
1893                         bUseTwoDigitYear, SbDateCorrection::None, dDate ))
1894                 break;
1895 
1896             rPar.Get(0)->PutDate(dDate);
1897 
1898             return;
1899         }
1900         while (false);
1901 
1902         SbxBase::SetError( ERRCODE_BASIC_BAD_PARAMETER );
1903     }
1904     else
1905     {
1906         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1907     }
1908 }
1909 
1910 void SbRtl_DateSerial(StarBASIC *, SbxArray & rPar, bool)
1911 {
1912     if (rPar.Count() < 4)
1913     {
1914         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1915     }
1916     sal_Int16 nYear = rPar.Get(1)->GetInteger();
1917     sal_Int16 nMonth = rPar.Get(2)->GetInteger();
1918     sal_Int16 nDay = rPar.Get(3)->GetInteger();
1919 
1920     double dDate;
1921     if( implDateSerial( nYear, nMonth, nDay, true, SbDateCorrection::RollOver, dDate ) )
1922     {
1923         rPar.Get(0)->PutDate(dDate);
1924     }
1925 }
1926 
1927 void SbRtl_TimeSerial(StarBASIC *, SbxArray & rPar, bool)
1928 {
1929     if (rPar.Count() < 4)
1930     {
1931         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1932     }
1933     sal_Int16 nHour = rPar.Get(1)->GetInteger();
1934     if ( nHour == 24 )
1935     {
1936         nHour = 0;                      // because of UNO DateTimes, which go till 24 o'clock
1937     }
1938     sal_Int16 nMinute = rPar.Get(2)->GetInteger();
1939     sal_Int16 nSecond = rPar.Get(3)->GetInteger();
1940     if ((nHour < 0 || nHour > 23)   ||
1941         (nMinute < 0 || nMinute > 59 )  ||
1942         (nSecond < 0 || nSecond > 59 ))
1943     {
1944         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1945     }
1946 
1947     rPar.Get(0)->PutDate(implTimeSerial(nHour, nMinute, nSecond)); // JSM
1948 }
1949 
1950 void SbRtl_DateValue(StarBASIC *, SbxArray & rPar, bool)
1951 {
1952     if (rPar.Count() < 2)
1953     {
1954         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
1955     }
1956     else
1957     {
1958         // #39629 check GetSbData()->pInst, can be called from the URL line
1959         std::shared_ptr<SvNumberFormatter> pFormatter;
1960         if( GetSbData()->pInst )
1961         {
1962             pFormatter = GetSbData()->pInst->GetNumberFormatter();
1963         }
1964         else
1965         {
1966             sal_uInt32 n;   // Dummy
1967             pFormatter = SbiInstance::PrepareNumberFormatter( n, n, n );
1968         }
1969 
1970         LanguageType eLangType = Application::GetSettings().GetLanguageTag().getLanguageType();
1971         sal_uInt32 nIndex = pFormatter->GetStandardIndex( eLangType);
1972         double fResult;
1973         OUString aStr(rPar.Get(1)->GetOUString());
1974         bool bSuccess = pFormatter->IsNumberFormat( aStr, nIndex, fResult );
1975         SvNumFormatType nType = pFormatter->GetType( nIndex );
1976 
1977         // DateValue("February 12, 1969") raises error if the system locale is not en_US
1978         // It seems that both locale number formatter and English number
1979         // formatter are supported in Visual Basic.
1980         if( !bSuccess && ( eLangType != LANGUAGE_ENGLISH_US ) )
1981         {
1982             // Try using LANGUAGE_ENGLISH_US to get the date value.
1983             nIndex = pFormatter->GetStandardIndex( LANGUAGE_ENGLISH_US);
1984             bSuccess = pFormatter->IsNumberFormat( aStr, nIndex, fResult );
1985             nType = pFormatter->GetType( nIndex );
1986         }
1987 
1988         if(bSuccess && (nType==SvNumFormatType::DATE || nType==SvNumFormatType::DATETIME))
1989         {
1990             if ( nType == SvNumFormatType::DATETIME )
1991             {
1992                 // cut time
1993                 if ( fResult  > 0.0 )
1994                 {
1995                     fResult = floor( fResult );
1996                 }
1997                 else
1998                 {
1999                     fResult = ceil( fResult );
2000                 }
2001             }
2002             rPar.Get(0)->PutDate(fResult);
2003         }
2004         else
2005         {
2006             StarBASIC::Error( ERRCODE_BASIC_CONVERSION );
2007         }
2008     }
2009 }
2010 
2011 void SbRtl_TimeValue(StarBASIC *, SbxArray & rPar, bool)
2012 {
2013     if (rPar.Count() < 2)
2014     {
2015         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2016     }
2017     else
2018     {
2019         std::shared_ptr<SvNumberFormatter> pFormatter;
2020         if( GetSbData()->pInst )
2021             pFormatter = GetSbData()->pInst->GetNumberFormatter();
2022         else
2023         {
2024             sal_uInt32 n;
2025             pFormatter = SbiInstance::PrepareNumberFormatter( n, n, n );
2026         }
2027 
2028         sal_uInt32 nIndex = 0;
2029         double fResult;
2030         bool bSuccess = pFormatter->IsNumberFormat(rPar.Get(1)->GetOUString(),
2031                                                    nIndex, fResult );
2032         SvNumFormatType nType = pFormatter->GetType(nIndex);
2033         if(bSuccess && (nType==SvNumFormatType::TIME||nType==SvNumFormatType::DATETIME))
2034         {
2035             if ( nType == SvNumFormatType::DATETIME )
2036             {
2037                 // cut days
2038                 fResult = fmod( fResult, 1 );
2039             }
2040             rPar.Get(0)->PutDate(fResult);
2041         }
2042         else
2043         {
2044             StarBASIC::Error( ERRCODE_BASIC_CONVERSION );
2045         }
2046     }
2047 }
2048 
2049 void SbRtl_Day(StarBASIC *, SbxArray & rPar, bool)
2050 {
2051     if (rPar.Count() < 2)
2052     {
2053         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2054     }
2055     else
2056     {
2057         SbxVariableRef pArg = rPar.Get(1);
2058         double aDate = pArg->GetDate();
2059 
2060         sal_Int16 nDay = implGetDateDay( aDate );
2061         rPar.Get(0)->PutInteger(nDay);
2062     }
2063 }
2064 
2065 void SbRtl_Year(StarBASIC *, SbxArray & rPar, bool)
2066 {
2067     if (rPar.Count() < 2)
2068     {
2069         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2070     }
2071     else
2072     {
2073         sal_Int16 nYear = implGetDateYear(rPar.Get(1)->GetDate());
2074         rPar.Get(0)->PutInteger(nYear);
2075     }
2076 }
2077 
2078 sal_Int16 implGetHour( double dDate )
2079 {
2080     double nFrac = dDate - floor( dDate );
2081     nFrac *= 86400.0;
2082     sal_Int32 nSeconds = static_cast<sal_Int32>(nFrac + 0.5);
2083     sal_Int16 nHour = static_cast<sal_Int16>(nSeconds / 3600);
2084     return nHour;
2085 }
2086 
2087 void SbRtl_Hour(StarBASIC *, SbxArray & rPar, bool)
2088 {
2089     if (rPar.Count() < 2)
2090     {
2091         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2092     }
2093     else
2094     {
2095         double nArg = rPar.Get(1)->GetDate();
2096         sal_Int16 nHour = implGetHour( nArg );
2097         rPar.Get(0)->PutInteger(nHour);
2098     }
2099 }
2100 
2101 void SbRtl_Minute(StarBASIC *, SbxArray & rPar, bool)
2102 {
2103     if (rPar.Count() < 2)
2104     {
2105         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2106     }
2107     else
2108     {
2109         double nArg = rPar.Get(1)->GetDate();
2110         sal_Int16 nMin = implGetMinute( nArg );
2111         rPar.Get(0)->PutInteger(nMin);
2112     }
2113 }
2114 
2115 void SbRtl_Month(StarBASIC *, SbxArray & rPar, bool)
2116 {
2117     if (rPar.Count() < 2)
2118     {
2119         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2120     }
2121     else
2122     {
2123         sal_Int16 nMonth = implGetDateMonth(rPar.Get(1)->GetDate());
2124         rPar.Get(0)->PutInteger(nMonth);
2125     }
2126 }
2127 
2128 sal_Int16 implGetSecond( double dDate )
2129 {
2130     double nFrac = dDate - floor( dDate );
2131     nFrac *= 86400.0;
2132     sal_Int32 nSeconds = static_cast<sal_Int32>(nFrac + 0.5);
2133     sal_Int16 nTemp = static_cast<sal_Int16>(nSeconds / 3600);
2134     nSeconds -= nTemp * 3600;
2135     nTemp = static_cast<sal_Int16>(nSeconds / 60);
2136     nSeconds -= nTemp * 60;
2137 
2138     sal_Int16 nRet = static_cast<sal_Int16>(nSeconds);
2139     return nRet;
2140 }
2141 
2142 void SbRtl_Second(StarBASIC *, SbxArray & rPar, bool)
2143 {
2144     if (rPar.Count() < 2)
2145     {
2146         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2147     }
2148     else
2149     {
2150         double nArg = rPar.Get(1)->GetDate();
2151         sal_Int16 nSecond = implGetSecond( nArg );
2152         rPar.Get(0)->PutInteger(nSecond);
2153     }
2154 }
2155 
2156 double Now_Impl()
2157 {
2158     DateTime aDateTime( DateTime::SYSTEM );
2159     double aSerial = static_cast<double>(GetDayDiff( aDateTime ));
2160     tools::Long nSeconds = aDateTime.GetHour();
2161     nSeconds *= 3600;
2162     nSeconds += aDateTime.GetMin() * 60;
2163     nSeconds += aDateTime.GetSec();
2164     double nDays = static_cast<double>(nSeconds) / (24.0*3600.0);
2165     aSerial += nDays;
2166     return aSerial;
2167 }
2168 
2169 // Date Now()
2170 
2171 void SbRtl_Now(StarBASIC*, SbxArray& rPar, bool) { rPar.Get(0)->PutDate(Now_Impl()); }
2172 
2173 // Date Time()
2174 
2175 void SbRtl_Time(StarBASIC *, SbxArray & rPar, bool bWrite)
2176 {
2177     if ( !bWrite )
2178     {
2179         tools::Time aTime( tools::Time::SYSTEM );
2180         SbxVariable* pMeth = rPar.Get(0);
2181         OUString aRes;
2182         if( pMeth->IsFixed() )
2183         {
2184             // Time$: hh:mm:ss
2185             char buf[ 20 ];
2186             snprintf( buf, sizeof(buf), "%02d:%02d:%02d",
2187                       aTime.GetHour(), aTime.GetMin(), aTime.GetSec() );
2188             aRes = OUString::createFromAscii( buf );
2189         }
2190         else
2191         {
2192             // Time: system dependent
2193             tools::Long nSeconds=aTime.GetHour();
2194             nSeconds *= 3600;
2195             nSeconds += aTime.GetMin() * 60;
2196             nSeconds += aTime.GetSec();
2197             double nDays = static_cast<double>(nSeconds) * ( 1.0 / (24.0*3600.0) );
2198             const Color* pCol;
2199 
2200             std::shared_ptr<SvNumberFormatter> pFormatter;
2201             sal_uInt32 nIndex;
2202             if( GetSbData()->pInst )
2203             {
2204                 pFormatter = GetSbData()->pInst->GetNumberFormatter();
2205                 nIndex = GetSbData()->pInst->GetStdTimeIdx();
2206             }
2207             else
2208             {
2209                 sal_uInt32 n;   // Dummy
2210                 pFormatter = SbiInstance::PrepareNumberFormatter( n, nIndex, n );
2211             }
2212 
2213             pFormatter->GetOutputString( nDays, nIndex, aRes, &pCol );
2214         }
2215         pMeth->PutString( aRes );
2216     }
2217     else
2218     {
2219         StarBASIC::Error( ERRCODE_BASIC_NOT_IMPLEMENTED );
2220     }
2221 }
2222 
2223 void SbRtl_Timer(StarBASIC *, SbxArray & rPar, bool)
2224 {
2225     tools::Time aTime( tools::Time::SYSTEM );
2226     tools::Long nSeconds = aTime.GetHour();
2227     nSeconds *= 3600;
2228     nSeconds += aTime.GetMin() * 60;
2229     nSeconds += aTime.GetSec();
2230     rPar.Get(0)->PutDate(static_cast<double>(nSeconds));
2231 }
2232 
2233 
2234 void SbRtl_Date(StarBASIC *, SbxArray & rPar, bool bWrite)
2235 {
2236     if ( !bWrite )
2237     {
2238         Date aToday( Date::SYSTEM );
2239         double nDays = static_cast<double>(GetDayDiff( aToday ));
2240         SbxVariable* pMeth = rPar.Get(0);
2241         if( pMeth->IsString() )
2242         {
2243             OUString aRes;
2244             const Color* pCol;
2245 
2246             std::shared_ptr<SvNumberFormatter> pFormatter;
2247             sal_uInt32 nIndex;
2248             if( GetSbData()->pInst )
2249             {
2250                 pFormatter = GetSbData()->pInst->GetNumberFormatter();
2251                 nIndex = GetSbData()->pInst->GetStdDateIdx();
2252             }
2253             else
2254             {
2255                 sal_uInt32 n;
2256                 pFormatter = SbiInstance::PrepareNumberFormatter( nIndex, n, n );
2257             }
2258 
2259             pFormatter->GetOutputString( nDays, nIndex, aRes, &pCol );
2260             pMeth->PutString( aRes );
2261         }
2262         else
2263         {
2264             pMeth->PutDate( nDays );
2265         }
2266     }
2267     else
2268     {
2269         StarBASIC::Error( ERRCODE_BASIC_NOT_IMPLEMENTED );
2270     }
2271 }
2272 
2273 void SbRtl_IsArray(StarBASIC *, SbxArray & rPar, bool)
2274 {
2275     if (rPar.Count() < 2)
2276     {
2277         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2278     }
2279     else
2280     {
2281         rPar.Get(0)->PutBool((rPar.Get(1)->GetType() & SbxARRAY) != 0);
2282     }
2283 }
2284 
2285 void SbRtl_IsObject(StarBASIC *, SbxArray & rPar, bool)
2286 {
2287     if (rPar.Count() < 2)
2288     {
2289         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2290     }
2291     else
2292     {
2293         SbxVariable* pVar = rPar.Get(1);
2294         bool bObject = pVar->IsObject();
2295         SbxBase* pObj = (bObject ? pVar->GetObject() : nullptr);
2296 
2297         if( auto pUnoClass = dynamic_cast<SbUnoClass*>( pObj) )
2298         {
2299             bObject = pUnoClass->getUnoClass().is();
2300         }
2301         rPar.Get(0)->PutBool(bObject);
2302     }
2303 }
2304 
2305 void SbRtl_IsDate(StarBASIC *, SbxArray & rPar, bool)
2306 {
2307     if (rPar.Count() < 2)
2308     {
2309         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2310     }
2311     else
2312     {
2313         // #46134 only string is converted, all other types result in sal_False
2314         SbxVariableRef xArg = rPar.Get(1);
2315         SbxDataType eType = xArg->GetType();
2316         bool bDate = false;
2317 
2318         if( eType == SbxDATE )
2319         {
2320             bDate = true;
2321         }
2322         else if( eType == SbxSTRING )
2323         {
2324             ErrCode nPrevError = SbxBase::GetError();
2325             SbxBase::ResetError();
2326 
2327             // force conversion of the parameter to SbxDATE
2328             xArg->SbxValue::GetDate();
2329 
2330             bDate = !SbxBase::IsError();
2331 
2332             SbxBase::ResetError();
2333             SbxBase::SetError( nPrevError );
2334         }
2335         rPar.Get(0)->PutBool(bDate);
2336     }
2337 }
2338 
2339 void SbRtl_IsEmpty(StarBASIC *, SbxArray & rPar, bool)
2340 {
2341     if (rPar.Count() < 2)
2342     {
2343         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2344     }
2345     else
2346     {
2347         SbxVariable* pVar = nullptr;
2348         if( SbiRuntime::isVBAEnabled() )
2349         {
2350             pVar = getDefaultProp(rPar.Get(1));
2351         }
2352         if ( pVar )
2353         {
2354             pVar->Broadcast( SfxHintId::BasicDataWanted );
2355             rPar.Get(0)->PutBool(pVar->IsEmpty());
2356         }
2357         else
2358         {
2359             rPar.Get(0)->PutBool(rPar.Get(1)->IsEmpty());
2360         }
2361     }
2362 }
2363 
2364 void SbRtl_IsError(StarBASIC *, SbxArray & rPar, bool)
2365 {
2366     if (rPar.Count() < 2)
2367     {
2368         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2369     }
2370     else
2371     {
2372         SbxVariable* pVar = rPar.Get(1);
2373         SbUnoObject* pObj = dynamic_cast<SbUnoObject*>( pVar  );
2374         if ( !pObj )
2375         {
2376             if ( SbxBase* pBaseObj = (pVar->IsObject() ? pVar->GetObject() : nullptr) )
2377             {
2378                 pObj = dynamic_cast<SbUnoObject*>( pBaseObj  );
2379             }
2380         }
2381         uno::Reference< script::XErrorQuery > xError;
2382         if ( pObj )
2383         {
2384             xError.set( pObj->getUnoAny(), uno::UNO_QUERY );
2385         }
2386         if ( xError.is() )
2387         {
2388             rPar.Get(0)->PutBool(xError->hasError());
2389         }
2390         else
2391         {
2392             rPar.Get(0)->PutBool(rPar.Get(1)->IsErr());
2393         }
2394     }
2395 }
2396 
2397 void SbRtl_IsNull(StarBASIC *, SbxArray & rPar, bool)
2398 {
2399     if (rPar.Count() < 2)
2400     {
2401         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2402     }
2403     else
2404     {
2405         // #51475 because of Uno-objects return true
2406         // even if the pObj value is NULL
2407         SbxVariableRef pArg = rPar.Get(1);
2408         bool bNull = rPar.Get(1)->IsNull();
2409         if( !bNull && pArg->GetType() == SbxOBJECT )
2410         {
2411             SbxBase* pObj = pArg->GetObject();
2412             if( !pObj )
2413             {
2414                 bNull = true;
2415             }
2416         }
2417         rPar.Get(0)->PutBool(bNull);
2418     }
2419 }
2420 
2421 void SbRtl_IsNumeric(StarBASIC *, SbxArray & rPar, bool)
2422 {
2423     if (rPar.Count() < 2)
2424     {
2425         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2426     }
2427     else
2428     {
2429         rPar.Get(0)->PutBool(rPar.Get(1)->IsNumericRTL());
2430     }
2431 }
2432 
2433 
2434 void SbRtl_IsMissing(StarBASIC *, SbxArray & rPar, bool)
2435 {
2436     if (rPar.Count() != 2)
2437         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2438 
2439     // #57915 Missing is reported by an error
2440     rPar.Get(0)->PutBool(rPar.Get(1)->IsErr());
2441 }
2442 
2443 // Function looks for wildcards, removes them and always returns the pure path
2444 static OUString implSetupWildcard(const OUString& rFileParam, SbiRTLData& rRTLData)
2445 {
2446     static const char cDelim1 = '/';
2447     static const char cDelim2 = '\\';
2448     static const char cWild1 = '*';
2449     static const char cWild2 = '?';
2450 
2451     rRTLData.moWildCard.reset();
2452     rRTLData.sFullNameToBeChecked.clear();
2453 
2454     OUString aFileParam = rFileParam;
2455     sal_Int32 nLastWild = aFileParam.lastIndexOf( cWild1 );
2456     if( nLastWild < 0 )
2457     {
2458         nLastWild = aFileParam.lastIndexOf( cWild2 );
2459     }
2460     bool bHasWildcards = ( nLastWild >= 0 );
2461 
2462 
2463     sal_Int32 nLastDelim = aFileParam.lastIndexOf( cDelim1 );
2464     if( nLastDelim < 0 )
2465     {
2466         nLastDelim = aFileParam.lastIndexOf( cDelim2 );
2467     }
2468     if( bHasWildcards )
2469     {
2470         // Wildcards in path?
2471         if( nLastDelim >= 0 && nLastDelim > nLastWild )
2472         {
2473             return aFileParam;
2474         }
2475     }
2476     else
2477     {
2478         OUString aPathStr = getFullPath( aFileParam );
2479         if( nLastDelim != aFileParam.getLength() - 1 )
2480         {
2481             rRTLData.sFullNameToBeChecked = aPathStr;
2482         }
2483         return aPathStr;
2484     }
2485 
2486     OUString aPureFileName;
2487     if( nLastDelim < 0 )
2488     {
2489         aPureFileName = aFileParam;
2490         aFileParam.clear();
2491     }
2492     else
2493     {
2494         aPureFileName = aFileParam.copy( nLastDelim + 1 );
2495         aFileParam = aFileParam.copy( 0, nLastDelim );
2496     }
2497 
2498     // Try again to get a valid URL/UNC-path with only the path
2499     OUString aPathStr = getFullPath( aFileParam );
2500 
2501     // Is there a pure file name left? Otherwise the path is
2502     // invalid anyway because it was not accepted by OSL before
2503     if (aPureFileName != "*")
2504     {
2505         rRTLData.moWildCard.emplace(aPureFileName);
2506     }
2507     return aPathStr;
2508 }
2509 
2510 static bool implCheckWildcard(std::u16string_view rName, SbiRTLData const& rRTLData)
2511 {
2512     bool bMatch = true;
2513 
2514     if (rRTLData.moWildCard)
2515     {
2516         bMatch = rRTLData.moWildCard->Matches(rName);
2517     }
2518     return bMatch;
2519 }
2520 
2521 
2522 static bool isRootDir( std::u16string_view aDirURLStr )
2523 {
2524     INetURLObject aDirURLObj( aDirURLStr );
2525     bool bRoot = false;
2526 
2527     // Check if it's a root directory
2528     sal_Int32 nCount = aDirURLObj.getSegmentCount();
2529 
2530     // No segment means Unix root directory "file:///"
2531     if( nCount == 0 )
2532     {
2533         bRoot = true;
2534     }
2535     // Exactly one segment needs further checking, because it
2536     // can be Unix "file:///foo/" -> no root
2537     // or Windows  "file:///c:/"  -> root
2538     else if( nCount == 1 )
2539     {
2540         OUString aSeg1 = aDirURLObj.getName( 0, true,
2541                                              INetURLObject::DecodeMechanism::WithCharset );
2542         if( aSeg1[1] == ':' )
2543         {
2544             bRoot = true;
2545         }
2546     }
2547     // More than one segments can never be root
2548     // so bRoot remains false
2549 
2550     return bRoot;
2551 }
2552 
2553 void SbRtl_Dir(StarBASIC *, SbxArray & rPar, bool)
2554 {
2555     OUString aPath;
2556 
2557     const sal_uInt32 nParCount = rPar.Count();
2558     if( nParCount > 3 )
2559     {
2560         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2561     }
2562     else
2563     {
2564         SbiRTLData& rRTLData = GetSbData()->pInst->GetRTLData();
2565 
2566         if( hasUno() )
2567         {
2568             const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
2569             if( xSFI.is() )
2570             {
2571                 if ( nParCount >= 2 )
2572                 {
2573                     OUString aFileParam = rPar.Get(1)->GetOUString();
2574 
2575                     OUString aFileURLStr = implSetupWildcard(aFileParam, rRTLData);
2576                     if (!rRTLData.sFullNameToBeChecked.isEmpty())
2577                     {
2578                         bool bExists = false;
2579                         try { bExists = xSFI->exists( aFileURLStr ); }
2580                         catch(const Exception & ) {}
2581 
2582                         OUString aNameOnlyStr;
2583                         if( bExists )
2584                         {
2585                             INetURLObject aFileURL( aFileURLStr );
2586                             aNameOnlyStr = aFileURL.getName( INetURLObject::LAST_SEGMENT,
2587                                                              true, INetURLObject::DecodeMechanism::WithCharset );
2588                         }
2589                         rPar.Get(0)->PutString(aNameOnlyStr);
2590                         return;
2591                     }
2592 
2593                     try
2594                     {
2595                         OUString aDirURLStr;
2596                         bool bFolder = xSFI->isFolder( aFileURLStr );
2597 
2598                         if( bFolder )
2599                         {
2600                             aDirURLStr = aFileURLStr;
2601                         }
2602                         else
2603                         {
2604                             rPar.Get(0)->PutString("");
2605                         }
2606 
2607                         SbAttributes nFlags = SbAttributes::NONE;
2608                         if ( nParCount > 2 )
2609                         {
2610                             rRTLData.nDirFlags = nFlags
2611                                 = static_cast<SbAttributes>(rPar.Get(2)->GetInteger());
2612                         }
2613                         else
2614                         {
2615                             rRTLData.nDirFlags = SbAttributes::NONE;
2616                         }
2617                         // Read directory
2618                         bool bIncludeFolders = bool(nFlags & SbAttributes::DIRECTORY);
2619                         rRTLData.aDirSeq = xSFI->getFolderContents(aDirURLStr, bIncludeFolders);
2620                         rRTLData.nCurDirPos = 0;
2621 
2622                         // #78651 Add "." and ".." directories for VB compatibility
2623                         if( bIncludeFolders )
2624                         {
2625                             bool bRoot = isRootDir( aDirURLStr );
2626 
2627                             // If it's no root directory we flag the need for
2628                             // the "." and ".." directories by the value -2
2629                             // for the actual position. Later for -2 will be
2630                             // returned "." and for -1 ".."
2631                             if( !bRoot )
2632                             {
2633                                 rRTLData.nCurDirPos = -2;
2634                             }
2635                         }
2636                     }
2637                     catch(const Exception & )
2638                     {
2639                     }
2640                 }
2641 
2642 
2643                 if (rRTLData.aDirSeq.hasElements())
2644                 {
2645                     bool bFolderFlag = bool(rRTLData.nDirFlags & SbAttributes::DIRECTORY);
2646 
2647                     SbiInstance* pInst = GetSbData()->pInst;
2648                     bool bCompatibility = ( pInst && pInst->IsCompatibility() );
2649                     for( ;; )
2650                     {
2651                         if (rRTLData.nCurDirPos < 0)
2652                         {
2653                             if (rRTLData.nCurDirPos == -2)
2654                             {
2655                                 aPath = ".";
2656                             }
2657                             else if (rRTLData.nCurDirPos == -1)
2658                             {
2659                                 aPath = "..";
2660                             }
2661                             rRTLData.nCurDirPos++;
2662                         }
2663                         else if (rRTLData.nCurDirPos >= rRTLData.aDirSeq.getLength())
2664                         {
2665                             rRTLData.aDirSeq.realloc(0);
2666                             aPath.clear();
2667                             break;
2668                         }
2669                         else
2670                         {
2671                             OUString aFile
2672                                 = rRTLData.aDirSeq.getConstArray()[rRTLData.nCurDirPos++];
2673 
2674                             if( bCompatibility )
2675                             {
2676                                 if( !bFolderFlag )
2677                                 {
2678                                     bool bFolder = xSFI->isFolder( aFile );
2679                                     if( bFolder )
2680                                     {
2681                                         continue;
2682                                     }
2683                                 }
2684                             }
2685                             else
2686                             {
2687                                 // Only directories
2688                                 if( bFolderFlag )
2689                                 {
2690                                     bool bFolder = xSFI->isFolder( aFile );
2691                                     if( !bFolder )
2692                                     {
2693                                         continue;
2694                                     }
2695                                 }
2696                             }
2697 
2698                             INetURLObject aURL( aFile );
2699                             aPath = aURL.getName( INetURLObject::LAST_SEGMENT, true,
2700                                                   INetURLObject::DecodeMechanism::WithCharset );
2701                         }
2702 
2703                         bool bMatch = implCheckWildcard(aPath, rRTLData);
2704                         if( !bMatch )
2705                         {
2706                             continue;
2707                         }
2708                         break;
2709                     }
2710                 }
2711                 rPar.Get(0)->PutString(aPath);
2712             }
2713         }
2714         else
2715         {
2716             // TODO: OSL
2717             if ( nParCount >= 2 )
2718             {
2719                 OUString aFileParam = rPar.Get(1)->GetOUString();
2720 
2721                 OUString aDirURL = implSetupWildcard(aFileParam, rRTLData);
2722 
2723                 SbAttributes nFlags = SbAttributes::NONE;
2724                 if ( nParCount > 2 )
2725                 {
2726                     rRTLData.nDirFlags = nFlags
2727                         = static_cast<SbAttributes>(rPar.Get(2)->GetInteger());
2728                 }
2729                 else
2730                 {
2731                     rRTLData.nDirFlags = SbAttributes::NONE;
2732                 }
2733 
2734                 // Read directory
2735                 bool bIncludeFolders = bool(nFlags & SbAttributes::DIRECTORY);
2736                 rRTLData.pDir = std::make_unique<Directory>(aDirURL);
2737                 FileBase::RC nRet = rRTLData.pDir->open();
2738                 if( nRet != FileBase::E_None )
2739                 {
2740                     rRTLData.pDir.reset();
2741                     rPar.Get(0)->PutString(OUString());
2742                     return;
2743                 }
2744 
2745                 // #86950 Add "." and ".." directories for VB compatibility
2746                 rRTLData.nCurDirPos = 0;
2747                 if( bIncludeFolders )
2748                 {
2749                     bool bRoot = isRootDir( aDirURL );
2750 
2751                     // If it's no root directory we flag the need for
2752                     // the "." and ".." directories by the value -2
2753                     // for the actual position. Later for -2 will be
2754                     // returned "." and for -1 ".."
2755                     if( !bRoot )
2756                     {
2757                         rRTLData.nCurDirPos = -2;
2758                     }
2759                 }
2760 
2761             }
2762 
2763             if (rRTLData.pDir)
2764             {
2765                 bool bFolderFlag = bool(rRTLData.nDirFlags & SbAttributes::DIRECTORY);
2766                 for( ;; )
2767                 {
2768                     if (rRTLData.nCurDirPos < 0)
2769                     {
2770                         if (rRTLData.nCurDirPos == -2)
2771                         {
2772                             aPath = ".";
2773                         }
2774                         else if (rRTLData.nCurDirPos == -1)
2775                         {
2776                             aPath = "..";
2777                         }
2778                         rRTLData.nCurDirPos++;
2779                     }
2780                     else
2781                     {
2782                         DirectoryItem aItem;
2783                         FileBase::RC nRet = rRTLData.pDir->getNextItem(aItem);
2784                         if( nRet != FileBase::E_None )
2785                         {
2786                             rRTLData.pDir.reset();
2787                             aPath.clear();
2788                             break;
2789                         }
2790 
2791                         // Handle flags
2792                         FileStatus aFileStatus( osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName );
2793                         nRet = aItem.getFileStatus( aFileStatus );
2794                         if( nRet != FileBase::E_None )
2795                         {
2796                             SAL_WARN("basic", "getFileStatus failed");
2797                             continue;
2798                         }
2799 
2800                         // Only directories?
2801                         if( bFolderFlag )
2802                         {
2803                             FileStatus::Type aType = aFileStatus.getFileType();
2804                             bool bFolder = isFolder( aType );
2805                             if( !bFolder )
2806                             {
2807                                 continue;
2808                             }
2809                         }
2810 
2811                         aPath = aFileStatus.getFileName();
2812                     }
2813 
2814                     bool bMatch = implCheckWildcard(aPath, rRTLData);
2815                     if( !bMatch )
2816                     {
2817                         continue;
2818                     }
2819                     break;
2820                 }
2821             }
2822             rPar.Get(0)->PutString(aPath);
2823         }
2824     }
2825 }
2826 
2827 
2828 void SbRtl_GetAttr(StarBASIC *, SbxArray & rPar, bool)
2829 {
2830     if (rPar.Count() == 2)
2831     {
2832         sal_Int16 nFlags = 0;
2833 
2834         // In Windows, we want to use Windows API to get the file attributes
2835         // for VBA interoperability.
2836     #if defined(_WIN32)
2837         if( SbiRuntime::isVBAEnabled() )
2838         {
2839             OUString aPathURL = getFullPath(rPar.Get(1)->GetOUString());
2840             OUString aPath;
2841             FileBase::getSystemPathFromFileURL( aPathURL, aPath );
2842             DWORD nRealFlags = GetFileAttributesW (o3tl::toW(aPath.getStr()));
2843             if (nRealFlags != 0xffffffff)
2844             {
2845                 if (nRealFlags == FILE_ATTRIBUTE_NORMAL)
2846                 {
2847                     nRealFlags = 0;
2848                 }
2849                 nFlags = static_cast<sal_Int16>(nRealFlags);
2850             }
2851             else
2852             {
2853                 StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
2854             }
2855             rPar.Get(0)->PutInteger(nFlags);
2856 
2857             return;
2858         }
2859     #endif
2860 
2861         if( hasUno() )
2862         {
2863             const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
2864             if( xSFI.is() )
2865             {
2866                 try
2867                 {
2868                     OUString aPath = getFullPath(rPar.Get(1)->GetOUString());
2869                     bool bExists = false;
2870                     try { bExists = xSFI->exists( aPath ); }
2871                     catch(const Exception & ) {}
2872                     if( !bExists )
2873                     {
2874                         return StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
2875                     }
2876 
2877                     bool bReadOnly = xSFI->isReadOnly( aPath );
2878                     bool bHidden = xSFI->isHidden( aPath );
2879                     bool bDirectory = xSFI->isFolder( aPath );
2880                     if( bReadOnly )
2881                     {
2882                         nFlags |= sal_uInt16(SbAttributes::READONLY);
2883                     }
2884                     if( bHidden )
2885                     {
2886                         nFlags |= sal_uInt16(SbAttributes::HIDDEN);
2887                     }
2888                     if( bDirectory )
2889                     {
2890                         nFlags |= sal_uInt16(SbAttributes::DIRECTORY);
2891                     }
2892                 }
2893                 catch(const Exception & )
2894                 {
2895                     StarBASIC::Error( ERRCODE_IO_GENERAL );
2896                 }
2897             }
2898         }
2899         else
2900         {
2901             DirectoryItem aItem;
2902             (void)DirectoryItem::get(getFullPath(rPar.Get(1)->GetOUString()), aItem);
2903             FileStatus aFileStatus( osl_FileStatus_Mask_Attributes | osl_FileStatus_Mask_Type );
2904             (void)aItem.getFileStatus( aFileStatus );
2905             sal_uInt64 nAttributes = aFileStatus.getAttributes();
2906             bool bReadOnly = (nAttributes & osl_File_Attribute_ReadOnly) != 0;
2907 
2908             FileStatus::Type aType = aFileStatus.getFileType();
2909             bool bDirectory = isFolder( aType );
2910             if( bReadOnly )
2911             {
2912                 nFlags |= sal_uInt16(SbAttributes::READONLY);
2913             }
2914             if( bDirectory )
2915             {
2916                 nFlags |= sal_uInt16(SbAttributes::DIRECTORY);
2917             }
2918         }
2919         rPar.Get(0)->PutInteger(nFlags);
2920     }
2921     else
2922     {
2923         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2924     }
2925 }
2926 
2927 
2928 void SbRtl_FileDateTime(StarBASIC *, SbxArray & rPar, bool)
2929 {
2930     if (rPar.Count() != 2)
2931     {
2932         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
2933     }
2934     else
2935     {
2936         OUString aPath = rPar.Get(1)->GetOUString();
2937         tools::Time aTime( tools::Time::EMPTY );
2938         Date aDate( Date::EMPTY );
2939         if( hasUno() )
2940         {
2941             const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
2942             if( xSFI.is() )
2943             {
2944                 try
2945                 {
2946                     util::DateTime aUnoDT = xSFI->getDateTimeModified( aPath );
2947                     aTime = tools::Time( aUnoDT );
2948                     aDate = Date( aUnoDT );
2949                 }
2950                 catch(const Exception & )
2951                 {
2952                     StarBASIC::Error( ERRCODE_IO_GENERAL );
2953                 }
2954             }
2955         }
2956         else
2957         {
2958             bool bSuccess = false;
2959             do
2960             {
2961                 DirectoryItem aItem;
2962                 if (DirectoryItem::get( getFullPath( aPath ), aItem ) != FileBase::E_None)
2963                     break;
2964 
2965                 FileStatus aFileStatus( osl_FileStatus_Mask_ModifyTime );
2966                 if (aItem.getFileStatus( aFileStatus ) != FileBase::E_None)
2967                     break;
2968 
2969                 TimeValue aTimeVal = aFileStatus.getModifyTime();
2970                 oslDateTime aDT;
2971                 if (!osl_getDateTimeFromTimeValue( &aTimeVal, &aDT ))
2972                     // Strictly spoken this is not an i/o error but some other failure.
2973                     break;
2974 
2975                 aTime = tools::Time( aDT.Hours, aDT.Minutes, aDT.Seconds, aDT.NanoSeconds );
2976                 aDate = Date( aDT.Day, aDT.Month, aDT.Year );
2977                 bSuccess = true;
2978             }
2979             while(false);
2980 
2981             if (!bSuccess)
2982                 StarBASIC::Error( ERRCODE_IO_GENERAL );
2983         }
2984 
2985         // An empty date shall not result in a formatted null-date (1899-12-30
2986         // or 1900-01-01) or even worse -0001-12-03 or some such due to how
2987         // GetDayDiff() treats things. There should be an error set in this
2988         // case anyway because of a missing file or other error above, but... so
2989         // do not even bother to use the number formatter.
2990         OUString aRes;
2991         if (aDate.IsEmpty())
2992         {
2993             aRes = "0000-00-00 00:00:00";
2994         }
2995         else
2996         {
2997             double fSerial = static_cast<double>(GetDayDiff( aDate ));
2998             tools::Long nSeconds = aTime.GetHour();
2999             nSeconds *= 3600;
3000             nSeconds += aTime.GetMin() * 60;
3001             nSeconds += aTime.GetSec();
3002             double nDays = static_cast<double>(nSeconds) / (24.0*3600.0);
3003             fSerial += nDays;
3004 
3005             const Color* pCol;
3006 
3007             std::shared_ptr<SvNumberFormatter> pFormatter;
3008             sal_uInt32 nIndex;
3009             if( GetSbData()->pInst )
3010             {
3011                 pFormatter = GetSbData()->pInst->GetNumberFormatter();
3012                 nIndex = GetSbData()->pInst->GetStdDateTimeIdx();
3013             }
3014             else
3015             {
3016                 sal_uInt32 n;
3017                 pFormatter = SbiInstance::PrepareNumberFormatter( n, n, nIndex );
3018             }
3019 
3020             pFormatter->GetOutputString( fSerial, nIndex, aRes, &pCol );
3021         }
3022         rPar.Get(0)->PutString(aRes);
3023     }
3024 }
3025 
3026 
3027 void SbRtl_EOF(StarBASIC *, SbxArray & rPar, bool)
3028 {
3029     // No changes for UCB
3030     if (rPar.Count() != 2)
3031     {
3032         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3033     }
3034     else
3035     {
3036         sal_Int16 nChannel = rPar.Get(1)->GetInteger();
3037         SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
3038         SbiStream* pSbStrm = pIO->GetStream( nChannel );
3039         if ( !pSbStrm )
3040         {
3041             return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
3042         }
3043         bool beof;
3044         SvStream* pSvStrm = pSbStrm->GetStrm();
3045         if ( pSbStrm->IsText() )
3046         {
3047             char cBla;
3048             (*pSvStrm).ReadChar( cBla ); // can we read another character?
3049             beof = pSvStrm->eof();
3050             if ( !beof )
3051             {
3052                 pSvStrm->SeekRel( -1 );
3053             }
3054         }
3055         else
3056         {
3057             beof = pSvStrm->eof();  // for binary data!
3058         }
3059         rPar.Get(0)->PutBool(beof);
3060     }
3061 }
3062 
3063 void SbRtl_FileAttr(StarBASIC *, SbxArray & rPar, bool)
3064 {
3065     // No changes for UCB
3066     // #57064 Although this function doesn't operate with DirEntry, it is
3067     // not touched by the adjustment to virtual URLs, as it only works on
3068     // already opened files and the name doesn't matter there.
3069 
3070     if (rPar.Count() != 3)
3071     {
3072         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3073     }
3074     else
3075     {
3076         sal_Int16 nChannel = rPar.Get(1)->GetInteger();
3077         SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
3078         SbiStream* pSbStrm = pIO->GetStream( nChannel );
3079         if ( !pSbStrm )
3080         {
3081             return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
3082         }
3083         sal_Int16 nRet;
3084         if (rPar.Get(2)->GetInteger() == 1)
3085         {
3086             nRet = static_cast<sal_Int16>(pSbStrm->GetMode());
3087         }
3088         else
3089         {
3090             nRet = 0; // System file handle not supported
3091         }
3092         rPar.Get(0)->PutInteger(nRet);
3093     }
3094 }
3095 void SbRtl_Loc(StarBASIC *, SbxArray & rPar, bool)
3096 {
3097     // No changes for UCB
3098     if (rPar.Count() != 2)
3099     {
3100         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3101     }
3102     else
3103     {
3104         sal_Int16 nChannel = rPar.Get(1)->GetInteger();
3105         SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
3106         SbiStream* pSbStrm = pIO->GetStream( nChannel );
3107         if ( !pSbStrm )
3108         {
3109             return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
3110         }
3111         SvStream* pSvStrm = pSbStrm->GetStrm();
3112         std::size_t nPos;
3113         if( pSbStrm->IsRandom())
3114         {
3115             short nBlockLen = pSbStrm->GetBlockLen();
3116             nPos = nBlockLen ? (pSvStrm->Tell() / nBlockLen) : 0;
3117             nPos++; // block positions starting at 1
3118         }
3119         else if ( pSbStrm->IsText() )
3120         {
3121             nPos = pSbStrm->GetLine();
3122         }
3123         else if( pSbStrm->IsBinary() )
3124         {
3125             nPos = pSvStrm->Tell();
3126         }
3127         else if ( pSbStrm->IsSeq() )
3128         {
3129             nPos = ( pSvStrm->Tell()+1 ) / 128;
3130         }
3131         else
3132         {
3133             nPos = pSvStrm->Tell();
3134         }
3135         rPar.Get(0)->PutLong(static_cast<sal_Int32>(nPos));
3136     }
3137 }
3138 
3139 void SbRtl_Lof(StarBASIC *, SbxArray & rPar, bool)
3140 {
3141     // No changes for UCB
3142     if (rPar.Count() != 2)
3143     {
3144         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3145     }
3146     else
3147     {
3148         sal_Int16 nChannel = rPar.Get(1)->GetInteger();
3149         SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
3150         SbiStream* pSbStrm = pIO->GetStream( nChannel );
3151         if ( !pSbStrm )
3152         {
3153             return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
3154         }
3155         SvStream* pSvStrm = pSbStrm->GetStrm();
3156         sal_uInt64 const nLen = pSvStrm->TellEnd();
3157         rPar.Get(0)->PutLong(static_cast<sal_Int32>(nLen));
3158     }
3159 }
3160 
3161 
3162 void SbRtl_Seek(StarBASIC *, SbxArray & rPar, bool)
3163 {
3164     // No changes for UCB
3165     int nArgs = static_cast<int>(rPar.Count());
3166     if ( nArgs < 2 || nArgs > 3 )
3167     {
3168         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3169     }
3170     sal_Int16 nChannel = rPar.Get(1)->GetInteger();
3171     SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
3172     SbiStream* pSbStrm = pIO->GetStream( nChannel );
3173     if ( !pSbStrm )
3174     {
3175         return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
3176     }
3177     SvStream* pStrm = pSbStrm->GetStrm();
3178 
3179     if ( nArgs == 2 )   // Seek-Function
3180     {
3181         sal_uInt64 nPos = pStrm->Tell();
3182         if( pSbStrm->IsRandom() )
3183         {
3184             nPos = nPos / pSbStrm->GetBlockLen();
3185         }
3186         nPos++; // Basic counts from 1
3187         rPar.Get(0)->PutLong(static_cast<sal_Int32>(nPos));
3188     }
3189     else                // Seek-Statement
3190     {
3191         sal_Int32 nPos = rPar.Get(2)->GetLong();
3192         if ( nPos < 1 )
3193         {
3194             return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3195         }
3196         nPos--; // Basic counts from 1, SvStreams count from 0
3197         pSbStrm->SetExpandOnWriteTo( 0 );
3198         if ( pSbStrm->IsRandom() )
3199         {
3200             nPos *= pSbStrm->GetBlockLen();
3201         }
3202         pStrm->Seek( static_cast<sal_uInt64>(nPos) );
3203         pSbStrm->SetExpandOnWriteTo( nPos );
3204     }
3205 }
3206 
3207 void SbRtl_Format(StarBASIC *, SbxArray & rPar, bool)
3208 {
3209     const sal_uInt32 nArgCount = rPar.Count();
3210     if ( nArgCount < 2 || nArgCount > 3 )
3211     {
3212         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3213     }
3214     else
3215     {
3216         OUString aResult;
3217         if( nArgCount == 2 )
3218         {
3219             rPar.Get(1)->Format(aResult);
3220         }
3221         else
3222         {
3223             OUString aFmt(rPar.Get(2)->GetOUString());
3224             rPar.Get(1)->Format(aResult, &aFmt);
3225         }
3226         rPar.Get(0)->PutString(aResult);
3227     }
3228 }
3229 
3230 static bool IsMissing(SbxArray& rPar, const sal_uInt32 i)
3231 {
3232     const sal_uInt32 nArgCount = rPar.Count();
3233     if (nArgCount <= i)
3234         return true;
3235 
3236     SbxVariable* aPar = rPar.Get(i);
3237     return (aPar->GetType() == SbxERROR && SbiRuntime::IsMissing(aPar, 1));
3238 }
3239 
3240 static sal_Int16 GetOptionalIntegerParamOrDefault(SbxArray& rPar, const sal_uInt32 i,
3241                                                   const sal_Int16 defaultValue)
3242 {
3243     return IsMissing(rPar, i) ? defaultValue : rPar.Get(i)->GetInteger();
3244 }
3245 
3246 static OUString GetOptionalOUStringParamOrDefault(SbxArray& rPar, const sal_uInt32 i,
3247                                                   const OUString& defaultValue)
3248 {
3249     return IsMissing(rPar, i) ? defaultValue : rPar.Get(i)->GetOUString();
3250 }
3251 
3252 static void lcl_FormatNumberPercent(SbxArray& rPar, bool isPercent)
3253 {
3254     const sal_uInt32 nArgCount = rPar.Count();
3255     if (nArgCount < 2 || nArgCount > 6)
3256     {
3257         return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
3258     }
3259 
3260     // The UI locale never changes -> we can use static value here
3261     static const LocaleDataWrapper localeData(Application::GetSettings().GetUILanguageTag());
3262     sal_Int16 nNumDigitsAfterDecimal = -1;
3263     if (nArgCount > 2 && !rPar.Get(2)->IsEmpty())
3264     {
3265         nNumDigitsAfterDecimal = rPar.Get(2)->GetInteger();
3266         if (nNumDigitsAfterDecimal < -1)
3267         {
3268             return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
3269         }
3270         else if (nNumDigitsAfterDecimal > 255)
3271             nNumDigitsAfterDecimal %= 256;
3272     }
3273     if (nNumDigitsAfterDecimal == -1)
3274         nNumDigitsAfterDecimal = LocaleDataWrapper::getNumDigits();
3275 
3276     bool bIncludeLeadingDigit = LocaleDataWrapper::isNumLeadingZero();
3277     if (nArgCount > 3 && !rPar.Get(3)->IsEmpty())
3278     {
3279         switch (rPar.Get(3)->GetInteger())
3280         {
3281             case ooo::vba::VbTriState::vbFalse:
3282                 bIncludeLeadingDigit = false;
3283                 break;
3284             case ooo::vba::VbTriState::vbTrue:
3285                 bIncludeLeadingDigit = true;
3286                 break;
3287             case ooo::vba::VbTriState::vbUseDefault:
3288                 // do nothing;
3289                 break;
3290             default:
3291                 return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
3292         }
3293     }
3294 
3295     bool bUseParensForNegativeNumbers = false;
3296     if (nArgCount > 4 && !rPar.Get(4)->IsEmpty())
3297     {
3298         switch (rPar.Get(4)->GetInteger())
3299         {
3300             case ooo::vba::VbTriState::vbFalse:
3301             case ooo::vba::VbTriState::vbUseDefault:
3302                 // do nothing
3303                 break;
3304             case ooo::vba::VbTriState::vbTrue:
3305                 bUseParensForNegativeNumbers = true;
3306                 break;
3307             default:
3308                 return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
3309         }
3310     }
3311 
3312     bool bGroupDigits = false;
3313     if (nArgCount > 5 && !rPar.Get(5)->IsEmpty())
3314     {
3315         switch (rPar.Get(5)->GetInteger())
3316         {
3317             case ooo::vba::VbTriState::vbFalse:
3318             case ooo::vba::VbTriState::vbUseDefault:
3319                 // do nothing
3320                 break;
3321             case ooo::vba::VbTriState::vbTrue:
3322                 bGroupDigits = true;
3323                 break;
3324             default:
3325                 return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
3326         }
3327     }
3328 
3329     double fVal = rPar.Get(1)->GetDouble();
3330     if (isPercent)
3331         fVal *= 100;
3332     const bool bNegative = fVal < 0;
3333     if (bNegative)
3334         fVal = fabs(fVal); // Always work with non-negatives, to easily handle leading zero
3335 
3336     static const sal_Unicode decSep = localeData.getNumDecimalSep().toChar();
3337     OUStringBuffer aResult;
3338     rtl::math::doubleToUStringBuffer(aResult,
3339         fVal, rtl_math_StringFormat_F, nNumDigitsAfterDecimal, decSep,
3340         bGroupDigits ? localeData.getDigitGrouping().getConstArray() : nullptr,
3341         localeData.getNumThousandSep().toChar());
3342 
3343     if (!bIncludeLeadingDigit && aResult.getLength() > 1)
3344         aResult.stripStart('0');
3345 
3346     if (nNumDigitsAfterDecimal > 0)
3347     {
3348         const sal_Int32 nSepPos = aResult.indexOf(decSep);
3349 
3350         // VBA allows up to 255 digits; rtl::math::doubleToUString outputs up to 15 digits
3351         // for ~small numbers, so pad them as appropriate.
3352         if (nSepPos >= 0)
3353             comphelper::string::padToLength(aResult, nSepPos + nNumDigitsAfterDecimal + 1, '0');
3354     }
3355 
3356     if (bNegative)
3357     {
3358         if (bUseParensForNegativeNumbers)
3359             aResult.insert(0, '(').append(')');
3360         else
3361             aResult.insert(0, '-');
3362     }
3363     if (isPercent)
3364         aResult.append('%');
3365     rPar.Get(0)->PutString(aResult.makeStringAndClear());
3366 }
3367 
3368 // https://docs.microsoft.com/en-us/office/vba/Language/Reference/User-Interface-Help/formatnumber-function
3369 void SbRtl_FormatNumber(StarBASIC*, SbxArray& rPar, bool)
3370 {
3371     return lcl_FormatNumberPercent(rPar, false);
3372 }
3373 
3374 // https://docs.microsoft.com/en-us/office/vba/Language/Reference/User-Interface-Help/formatpercent-function
3375 void SbRtl_FormatPercent(StarBASIC*, SbxArray& rPar, bool)
3376 {
3377     return lcl_FormatNumberPercent(rPar, true);
3378 }
3379 
3380 namespace {
3381 
3382 // note: BASIC does not use comphelper::random, because
3383 // Randomize(int) must be supported and should not affect non-BASIC random use
3384 struct RandomNumberGenerator
3385 {
3386     std::mt19937 global_rng;
3387 
3388     RandomNumberGenerator()
3389     {
3390         try
3391         {
3392             std::random_device rd;
3393             // initialises the state of the global random number generator
3394             // should only be called once.
3395             // (note, a few std::variate_generator<> (like normal) have their
3396             // own state which would need a reset as well to guarantee identical
3397             // sequence of numbers, e.g. via myrand.distribution().reset())
3398             global_rng.seed(rd() ^ time(nullptr));
3399         }
3400         catch (std::runtime_error& e)
3401         {
3402             SAL_WARN("basic", "Using std::random_device failed: " << e.what());
3403             global_rng.seed(time(nullptr));
3404         }
3405     }
3406 };
3407 
3408 RandomNumberGenerator& theRandomNumberGenerator()
3409 {
3410     static RandomNumberGenerator theGenerator;
3411     return theGenerator;
3412 }
3413 
3414 }
3415 
3416 void SbRtl_Randomize(StarBASIC *, SbxArray & rPar, bool)
3417 {
3418     if (rPar.Count() > 2)
3419     {
3420         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3421     }
3422     if (rPar.Count() == 2)
3423     {
3424         int nSeed = static_cast<int>(rPar.Get(1)->GetInteger());
3425         theRandomNumberGenerator().global_rng.seed(nSeed);
3426     }
3427     // without parameter, no need to do anything - RNG is seeded at first use
3428 }
3429 
3430 void SbRtl_Rnd(StarBASIC *, SbxArray & rPar, bool)
3431 {
3432     if (rPar.Count() > 2)
3433     {
3434         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3435     }
3436     else
3437     {
3438         std::uniform_real_distribution<double> dist(0.0, 1.0);
3439         double const tmp(dist(theRandomNumberGenerator().global_rng));
3440         rPar.Get(0)->PutDouble(tmp);
3441     }
3442 }
3443 
3444 
3445 //  Syntax: Shell("Path",[ Window-Style,[ "Params", [ bSync = sal_False ]]])
3446 //  WindowStyles (VBA compatible):
3447 //      2 == Minimized
3448 //      3 == Maximized
3449 //     10 == Full-Screen (text mode applications OS/2, WIN95, WNT)
3450 //     HACK: The WindowStyle will be passed to
3451 //     Application::StartApp in Creator. Format: "xxxx2"
3452 
3453 
3454 void SbRtl_Shell(StarBASIC *, SbxArray & rPar, bool)
3455 {
3456     const sal_uInt32 nArgCount = rPar.Count();
3457     if ( nArgCount < 2 || nArgCount > 5 )
3458     {
3459         rPar.Get(0)->PutLong(0);
3460         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3461     }
3462     else
3463     {
3464         oslProcessOption nOptions = osl_Process_SEARCHPATH | osl_Process_DETACHED;
3465 
3466         OUString aCmdLine = rPar.Get(1)->GetOUString();
3467         // attach additional parameters - everything must be parsed anyway
3468         if( nArgCount >= 4 )
3469         {
3470             OUString tmp = rPar.Get(3)->GetOUString().trim();
3471             if (!tmp.isEmpty())
3472             {
3473                 aCmdLine += " " + tmp;
3474             }
3475         }
3476         else if( aCmdLine.isEmpty() )
3477         {
3478             // avoid special treatment (empty list)
3479             aCmdLine += " ";
3480         }
3481         sal_Int32 nLen = aCmdLine.getLength();
3482 
3483         // #55735 if there are parameters, they have to be separated
3484         // #72471 also separate the single parameters
3485         std::vector<OUString> aTokenVector;
3486         OUString aToken;
3487         sal_Int32 i = 0;
3488         sal_Unicode c;
3489         while( i < nLen )
3490         {
3491             for ( ;; ++i )
3492             {
3493                 c = aCmdLine[ i ];
3494                 if ( c != ' ' && c != '\t' )
3495                 {
3496                     break;
3497                 }
3498             }
3499 
3500             if( c == '\"' || c == '\'' )
3501             {
3502                 sal_Int32 iFoundPos = aCmdLine.indexOf( c, i + 1 );
3503 
3504                 if( iFoundPos < 0 )
3505                 {
3506                     aToken = aCmdLine.copy( i);
3507                     i = nLen;
3508                 }
3509                 else
3510                 {
3511                     aToken = aCmdLine.copy( i + 1, (iFoundPos - i - 1) );
3512                     i = iFoundPos + 1;
3513                 }
3514             }
3515             else
3516             {
3517                 sal_Int32 iFoundSpacePos = aCmdLine.indexOf( ' ', i );
3518                 sal_Int32 iFoundTabPos = aCmdLine.indexOf( '\t', i );
3519                 sal_Int32 iFoundPos = iFoundSpacePos >= 0 ? iFoundTabPos >= 0 ? std::min( iFoundSpacePos, iFoundTabPos ) : iFoundSpacePos : -1;
3520 
3521                 if( iFoundPos < 0 )
3522                 {
3523                     aToken = aCmdLine.copy( i );
3524                     i = nLen;
3525                 }
3526                 else
3527                 {
3528                     aToken = aCmdLine.copy( i, (iFoundPos - i) );
3529                     i = iFoundPos;
3530                 }
3531             }
3532 
3533             // insert into the list
3534             aTokenVector.push_back( aToken );
3535         }
3536         // #55735 / #72471 end
3537 
3538         sal_Int16 nWinStyle = 0;
3539         if( nArgCount >= 3 )
3540         {
3541             nWinStyle = rPar.Get(2)->GetInteger();
3542             switch( nWinStyle )
3543             {
3544             case 2:
3545                 nOptions |= osl_Process_MINIMIZED;
3546                 break;
3547             case 3:
3548                 nOptions |= osl_Process_MAXIMIZED;
3549                 break;
3550             case 10:
3551                 nOptions |= osl_Process_FULLSCREEN;
3552                 break;
3553             }
3554 
3555             bool bSync = false;
3556             if( nArgCount >= 5 )
3557             {
3558                 bSync = rPar.Get(4)->GetBool();
3559             }
3560             if( bSync )
3561             {
3562                 nOptions |= osl_Process_WAIT;
3563             }
3564         }
3565 
3566         // #72471 work parameter(s) up
3567         std::vector<OUString>::const_iterator iter = aTokenVector.begin();
3568         OUString aOUStrProgURL = getFullPath( *iter );
3569 
3570         ++iter;
3571 
3572         sal_uInt16 nParamCount = sal::static_int_cast< sal_uInt16 >(aTokenVector.size() - 1 );
3573         std::unique_ptr<rtl_uString*[]> pParamList;
3574         if( nParamCount )
3575         {
3576             pParamList.reset( new rtl_uString*[nParamCount]);
3577             for(int iVector = 0; iter != aTokenVector.end(); ++iVector, ++iter)
3578             {
3579                 const OUString& rParamStr = *iter;
3580                 pParamList[iVector] = nullptr;
3581                 rtl_uString_assign(&(pParamList[iVector]), rParamStr.pData);
3582             }
3583         }
3584 
3585         oslProcess pApp;
3586         bool bSucc = osl_executeProcess(
3587                     aOUStrProgURL.pData,
3588                     pParamList.get(),
3589                     nParamCount,
3590                     nOptions,
3591                     nullptr,
3592                     nullptr,
3593                     nullptr, 0,
3594                     &pApp ) == osl_Process_E_None;
3595 
3596         // 53521 only free process handle on success
3597         if (bSucc)
3598         {
3599             osl_freeProcessHandle( pApp );
3600         }
3601 
3602         for(int j = 0; j < nParamCount; ++j)
3603         {
3604             rtl_uString_release(pParamList[j]);
3605         }
3606 
3607         if( !bSucc )
3608         {
3609             StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
3610         }
3611         else
3612         {
3613             rPar.Get(0)->PutLong(0);
3614         }
3615     }
3616 }
3617 
3618 void SbRtl_VarType(StarBASIC *, SbxArray & rPar, bool)
3619 {
3620     if (rPar.Count() != 2)
3621     {
3622         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3623     }
3624     else
3625     {
3626         SbxDataType eType = rPar.Get(1)->GetType();
3627         rPar.Get(0)->PutInteger(static_cast<sal_Int16>(eType));
3628     }
3629 }
3630 
3631 // Exported function
3632 OUString getBasicTypeName( SbxDataType eType )
3633 {
3634     static const char* pTypeNames[] =
3635     {
3636         "Empty",            // SbxEMPTY
3637         "Null",             // SbxNULL
3638         "Integer",          // SbxINTEGER
3639         "Long",             // SbxLONG
3640         "Single",           // SbxSINGLE
3641         "Double",           // SbxDOUBLE
3642         "Currency",         // SbxCURRENCY
3643         "Date",             // SbxDATE
3644         "String",           // SbxSTRING
3645         "Object",           // SbxOBJECT
3646         "Error",            // SbxERROR
3647         "Boolean",          // SbxBOOL
3648         "Variant",          // SbxVARIANT
3649         "DataObject",       // SbxDATAOBJECT
3650         "Unknown Type",
3651         "Unknown Type",
3652         "Char",             // SbxCHAR
3653         "Byte",             // SbxBYTE
3654         "UShort",           // SbxUSHORT
3655         "ULong",            // SbxULONG
3656         "Long64",           // SbxLONG64
3657         "ULong64",          // SbxULONG64
3658         "Int",              // SbxINT
3659         "UInt",             // SbxUINT
3660         "Void",             // SbxVOID
3661         "HResult",          // SbxHRESULT
3662         "Pointer",          // SbxPOINTER
3663         "DimArray",         // SbxDIMARRAY
3664         "CArray",           // SbxCARRAY
3665         "Userdef",          // SbxUSERDEF
3666         "Lpstr",            // SbxLPSTR
3667         "Lpwstr",           // SbxLPWSTR
3668         "Unknown Type",     // SbxCoreSTRING
3669         "WString",          // SbxWSTRING
3670         "WChar",            // SbxWCHAR
3671         "Int64",            // SbxSALINT64
3672         "UInt64",           // SbxSALUINT64
3673         "Decimal",          // SbxDECIMAL
3674     };
3675 
3676     size_t nPos = static_cast<size_t>(eType) & 0x0FFF;
3677     const size_t nTypeNameCount = std::size( pTypeNames );
3678     if ( nPos >= nTypeNameCount )
3679     {
3680         nPos = nTypeNameCount - 1;
3681     }
3682     return OUString::createFromAscii(pTypeNames[nPos]);
3683 }
3684 
3685 static OUString getObjectTypeName( SbxVariable* pVar )
3686 {
3687     OUString sRet( "Object" );
3688     if ( pVar )
3689     {
3690         SbxBase* pBaseObj = pVar->GetObject();
3691         if( !pBaseObj )
3692         {
3693            sRet = "Nothing";
3694         }
3695         else
3696         {
3697             SbUnoObject* pUnoObj = dynamic_cast<SbUnoObject*>( pVar  );
3698             if ( !pUnoObj )
3699             {
3700                 pUnoObj = dynamic_cast<SbUnoObject*>( pBaseObj  );
3701             }
3702             if ( pUnoObj )
3703             {
3704                 Any aObj = pUnoObj->getUnoAny();
3705                 // For upstreaming unless we start to build oovbaapi by default
3706                 // we need to get detect the vba-ness of the object in some
3707                 // other way
3708                 // note: Automation objects do not support XServiceInfo
3709                 uno::Reference< XServiceInfo > xServInfo( aObj, uno::UNO_QUERY );
3710                 if ( xServInfo.is() )
3711                 {
3712                     // is this a VBA object ?
3713                     Sequence< OUString > sServices = xServInfo->getSupportedServiceNames();
3714                     if ( sServices.hasElements() )
3715                     {
3716                         sRet = sServices[ 0 ];
3717                     }
3718                 }
3719                 else
3720                 {
3721                     uno::Reference< bridge::oleautomation::XAutomationObject > xAutoMation( aObj, uno::UNO_QUERY );
3722                     if ( xAutoMation.is() )
3723                     {
3724                         uno::Reference< script::XInvocation > xInv( aObj, uno::UNO_QUERY );
3725                         if ( xInv.is() )
3726                         {
3727                             try
3728                             {
3729                                 xInv->getValue( "$GetTypeName" ) >>= sRet;
3730                             }
3731                             catch(const Exception& )
3732                             {
3733                             }
3734                         }
3735                     }
3736                 }
3737                 sal_Int32 nDot = sRet.lastIndexOf( '.' );
3738                 if ( nDot != -1 && nDot < sRet.getLength() )
3739                 {
3740                     sRet = sRet.copy( nDot + 1 );
3741                 }
3742             }
3743         }
3744     }
3745     return sRet;
3746 }
3747 
3748 void SbRtl_TypeName(StarBASIC *, SbxArray & rPar, bool)
3749 {
3750     if (rPar.Count() != 2)
3751     {
3752         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3753     }
3754     else
3755     {
3756         SbxDataType eType = rPar.Get(1)->GetType();
3757         bool bIsArray = ( ( eType & SbxARRAY ) != 0 );
3758 
3759         OUString aRetStr;
3760         if ( SbiRuntime::isVBAEnabled() && eType == SbxOBJECT )
3761         {
3762             aRetStr = getObjectTypeName(rPar.Get(1));
3763         }
3764         else
3765         {
3766             aRetStr = getBasicTypeName( eType );
3767         }
3768         if( bIsArray )
3769         {
3770             aRetStr += "()";
3771         }
3772         rPar.Get(0)->PutString(aRetStr);
3773     }
3774 }
3775 
3776 void SbRtl_Len(StarBASIC *, SbxArray & rPar, bool)
3777 {
3778     if (rPar.Count() != 2)
3779     {
3780         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3781     }
3782     else
3783     {
3784         const OUString& rStr = rPar.Get(1)->GetOUString();
3785         rPar.Get(0)->PutLong(rStr.getLength());
3786     }
3787 }
3788 
3789 void SbRtl_DDEInitiate(StarBASIC *, SbxArray & rPar, bool)
3790 {
3791     int nArgs = static_cast<int>(rPar.Count());
3792     if ( nArgs != 3 )
3793     {
3794         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3795     }
3796     const OUString& rApp = rPar.Get(1)->GetOUString();
3797     const OUString& rTopic = rPar.Get(2)->GetOUString();
3798 
3799     SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
3800     size_t nChannel;
3801     ErrCode nDdeErr = pDDE->Initiate( rApp, rTopic, nChannel );
3802     if( nDdeErr )
3803     {
3804         StarBASIC::Error( nDdeErr );
3805     }
3806     else
3807     {
3808         rPar.Get(0)->PutInteger(static_cast<sal_Int16>(nChannel));
3809     }
3810 }
3811 
3812 void SbRtl_DDETerminate(StarBASIC *, SbxArray & rPar, bool)
3813 {
3814     rPar.Get(0)->PutEmpty();
3815     int nArgs = static_cast<int>(rPar.Count());
3816     if ( nArgs != 2 )
3817     {
3818         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3819         return;
3820     }
3821     size_t nChannel = rPar.Get(1)->GetInteger();
3822     SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
3823     ErrCode nDdeErr = pDDE->Terminate( nChannel );
3824     if( nDdeErr )
3825     {
3826         StarBASIC::Error( nDdeErr );
3827     }
3828 }
3829 
3830 void SbRtl_DDETerminateAll(StarBASIC *, SbxArray & rPar, bool)
3831 {
3832     rPar.Get(0)->PutEmpty();
3833     int nArgs = static_cast<int>(rPar.Count());
3834     if ( nArgs != 1 )
3835     {
3836         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3837     }
3838 
3839     SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
3840     ErrCode nDdeErr = pDDE->TerminateAll();
3841     if( nDdeErr )
3842     {
3843         StarBASIC::Error( nDdeErr );
3844     }
3845 }
3846 
3847 void SbRtl_DDERequest(StarBASIC *, SbxArray & rPar, bool)
3848 {
3849     int nArgs = static_cast<int>(rPar.Count());
3850     if ( nArgs != 3 )
3851     {
3852         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3853     }
3854     size_t nChannel = rPar.Get(1)->GetInteger();
3855     const OUString& rItem = rPar.Get(2)->GetOUString();
3856     SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
3857     OUString aResult;
3858     ErrCode nDdeErr = pDDE->Request( nChannel, rItem, aResult );
3859     if( nDdeErr )
3860     {
3861         StarBASIC::Error( nDdeErr );
3862     }
3863     else
3864     {
3865         rPar.Get(0)->PutString(aResult);
3866     }
3867 }
3868 
3869 void SbRtl_DDEExecute(StarBASIC *, SbxArray & rPar, bool)
3870 {
3871     rPar.Get(0)->PutEmpty();
3872     int nArgs = static_cast<int>(rPar.Count());
3873     if ( nArgs != 3 )
3874     {
3875         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3876     }
3877     size_t nChannel = rPar.Get(1)->GetInteger();
3878     const OUString& rCommand = rPar.Get(2)->GetOUString();
3879     SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
3880     ErrCode nDdeErr = pDDE->Execute( nChannel, rCommand );
3881     if( nDdeErr )
3882     {
3883         StarBASIC::Error( nDdeErr );
3884     }
3885 }
3886 
3887 void SbRtl_DDEPoke(StarBASIC *, SbxArray & rPar, bool)
3888 {
3889     rPar.Get(0)->PutEmpty();
3890     int nArgs = static_cast<int>(rPar.Count());
3891     if ( nArgs != 4 )
3892     {
3893         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3894     }
3895     size_t nChannel = rPar.Get(1)->GetInteger();
3896     const OUString& rItem = rPar.Get(2)->GetOUString();
3897     const OUString& rData = rPar.Get(3)->GetOUString();
3898     SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
3899     ErrCode nDdeErr = pDDE->Poke( nChannel, rItem, rData );
3900     if( nDdeErr )
3901     {
3902         StarBASIC::Error( nDdeErr );
3903     }
3904 }
3905 
3906 
3907 void SbRtl_FreeFile(StarBASIC *, SbxArray & rPar, bool)
3908 {
3909     if (rPar.Count() != 1)
3910     {
3911         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3912     }
3913     SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
3914     short nChannel = 1;
3915     while( nChannel < CHANNELS )
3916     {
3917         SbiStream* pStrm = pIO->GetStream( nChannel );
3918         if( !pStrm )
3919         {
3920             rPar.Get(0)->PutInteger(nChannel);
3921             return;
3922         }
3923         nChannel++;
3924     }
3925     StarBASIC::Error( ERRCODE_BASIC_TOO_MANY_FILES );
3926 }
3927 
3928 void SbRtl_LBound(StarBASIC *, SbxArray & rPar, bool)
3929 {
3930     const sal_uInt32 nParCount = rPar.Count();
3931     if ( nParCount != 3 && nParCount != 2 )
3932         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3933 
3934     SbxBase* pParObj = rPar.Get(1)->GetObject();
3935     SbxDimArray* pArr = dynamic_cast<SbxDimArray*>( pParObj );
3936     if( !pArr )
3937         return StarBASIC::Error( ERRCODE_BASIC_MUST_HAVE_DIMS );
3938 
3939     sal_Int32 nLower, nUpper;
3940     short nDim = (nParCount == 3) ? static_cast<short>(rPar.Get(2)->GetInteger()) : 1;
3941     if (!pArr->GetDim(nDim, nLower, nUpper))
3942         return StarBASIC::Error( ERRCODE_BASIC_OUT_OF_RANGE );
3943     rPar.Get(0)->PutLong(nLower);
3944 }
3945 
3946 void SbRtl_UBound(StarBASIC *, SbxArray & rPar, bool)
3947 {
3948     const sal_uInt32 nParCount = rPar.Count();
3949     if ( nParCount != 3 && nParCount != 2 )
3950         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3951 
3952     SbxBase* pParObj = rPar.Get(1)->GetObject();
3953     SbxDimArray* pArr = dynamic_cast<SbxDimArray*>( pParObj );
3954     if( !pArr )
3955         return StarBASIC::Error( ERRCODE_BASIC_MUST_HAVE_DIMS );
3956 
3957     sal_Int32 nLower, nUpper;
3958     short nDim = (nParCount == 3) ? static_cast<short>(rPar.Get(2)->GetInteger()) : 1;
3959     if (!pArr->GetDim(nDim, nLower, nUpper))
3960         return StarBASIC::Error( ERRCODE_BASIC_OUT_OF_RANGE );
3961     rPar.Get(0)->PutLong(nUpper);
3962 }
3963 
3964 void SbRtl_RGB(StarBASIC *, SbxArray & rPar, bool)
3965 {
3966     if (rPar.Count() != 4)
3967         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
3968 
3969     sal_Int32 nRed = rPar.Get(1)->GetInteger() & 0xFF;
3970     sal_Int32 nGreen = rPar.Get(2)->GetInteger() & 0xFF;
3971     sal_Int32 nBlue = rPar.Get(3)->GetInteger() & 0xFF;
3972     sal_Int32 nRGB;
3973 
3974     SbiInstance* pInst = GetSbData()->pInst;
3975     bool bCompatibility = ( pInst && pInst->IsCompatibility() );
3976     // See discussion in tdf#145725, here's the quotation from a link indicated in the bugtracker
3977     // which explains why we need to manage RGB differently according to VB compatibility
3978     // "In other words, the individual color components are stored in the opposite order one would expect.
3979     //  VB stores the red color component in the low-order byte of the long integer's low-order word,
3980     //  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"
3981     if( bCompatibility )
3982     {
3983         nRGB   = (nBlue << 16) | (nGreen << 8) | nRed;
3984     }
3985     else
3986     {
3987         nRGB   = (nRed << 16) | (nGreen << 8) | nBlue;
3988     }
3989     rPar.Get(0)->PutLong(nRGB);
3990 }
3991 
3992 void SbRtl_QBColor(StarBASIC *, SbxArray & rPar, bool)
3993 {
3994     static const sal_Int32 pRGB[] =
3995     {
3996         0x000000,
3997         0x800000,
3998         0x008000,
3999         0x808000,
4000         0x000080,
4001         0x800080,
4002         0x008080,
4003         0xC0C0C0,
4004         0x808080,
4005         0xFF0000,
4006         0x00FF00,
4007         0xFFFF00,
4008         0x0000FF,
4009         0xFF00FF,
4010         0x00FFFF,
4011         0xFFFFFF,
4012     };
4013 
4014     if (rPar.Count() != 2)
4015     {
4016         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
4017     }
4018 
4019     sal_Int16 nCol = rPar.Get(1)->GetInteger();
4020     if( nCol < 0 || nCol > 15 )
4021     {
4022         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
4023     }
4024     sal_Int32 nRGB = pRGB[ nCol ];
4025     rPar.Get(0)->PutLong(nRGB);
4026 }
4027 
4028 static std::vector<sal_uInt8> byteArray2Vec(SbxArray* pArr)
4029 {
4030     std::vector<sal_uInt8> result;
4031     if (pArr)
4032     {
4033         const sal_uInt32 nCount = pArr->Count();
4034         result.reserve(nCount + 1); // to avoid reallocation when padding in vbFromUnicode
4035         for (sal_uInt32 i = 0; i < nCount; i++)
4036             result.push_back(pArr->Get(i)->GetByte());
4037     }
4038     return result;
4039 }
4040 
4041 // Makes sure to get the byte array if passed, or the string converted to the bytes using
4042 // StringToByteArray in basic/source/sbx/sbxstr.cxx
4043 static std::vector<sal_uInt8> getByteArray(SbxValue& val)
4044 {
4045     if (val.GetFullType() == SbxOBJECT)
4046         if (auto pObj = val.GetObject())
4047             if (pObj->GetType() == (SbxARRAY | SbxBYTE))
4048                 if (auto pArr = dynamic_cast<SbxArray*>(pObj))
4049                     return byteArray2Vec(pArr);
4050 
4051     // Convert to string
4052     tools::SvRef<SbxValue> pStringValue(new SbxValue(SbxSTRING));
4053     *pStringValue = val;
4054 
4055     // Convert string to byte array
4056     tools::SvRef<SbxValue> pValue(new SbxValue(SbxOBJECT));
4057     pValue->PutObject(new SbxArray(SbxBYTE));
4058     *pValue = *pStringValue; // Does the magic of conversion of strings to byte arrays
4059     return byteArray2Vec(dynamic_cast<SbxArray*>(pValue->GetObject()));
4060 }
4061 
4062 // StrConv(string, conversion, LCID)
4063 void SbRtl_StrConv(StarBASIC *, SbxArray & rPar, bool)
4064 {
4065     const sal_uInt32 nArgCount = rPar.Count() - 1;
4066     if( nArgCount < 2 || nArgCount > 3 )
4067     {
4068         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
4069     }
4070 
4071     sal_Int32 nConversion = rPar.Get(2)->GetLong();
4072     LanguageType nLanguage = LANGUAGE_SYSTEM;
4073     if (nArgCount == 3)
4074     {
4075         sal_Int32 lcid = rPar.Get(3)->GetLong();
4076         nLanguage = LanguageType(lcid);
4077     }
4078 
4079     if (nConversion == ooo::vba::VbStrConv::vbUnicode) // This mode does not combine
4080     {
4081         // Assume that the passed byte array is encoded in the defined encoding, convert to
4082         // UTF-16 and store as string. Passed strings are converted to byte array first.
4083         auto inArray = getByteArray(*rPar.Get(1));
4084         std::string_view s(reinterpret_cast<char*>(inArray.data()), inArray.size() / sizeof(char));
4085         const auto encoding = utl_getWinTextEncodingFromLangStr(LanguageTag(nLanguage).getBcp47());
4086         OUString aOUStr = OStringToOUString(s, encoding);
4087         rPar.Get(0)->PutString(aOUStr);
4088         return;
4089     }
4090 
4091     if (nConversion == ooo::vba::VbStrConv::vbFromUnicode) // This mode does not combine
4092     {
4093         // Assume that the passed byte array is UTF-16-encoded (system-endian), convert to specified
4094         // encoding and store as byte array. Passed strings are converted to byte array first.
4095         auto inArray = getByteArray(*rPar.Get(1));
4096         while (inArray.size() % sizeof(sal_Unicode))
4097             inArray.push_back('\0');
4098         std::u16string_view s(reinterpret_cast<sal_Unicode*>(inArray.data()),
4099                               inArray.size() / sizeof(sal_Unicode));
4100         const auto encoding = utl_getWinTextEncodingFromLangStr(LanguageTag(nLanguage).getBcp47());
4101         OString aOStr = OUStringToOString(s, encoding);
4102         const sal_Int32 lb = IsBaseIndexOne() ? 1 : 0;
4103         const sal_Int32 ub = lb + aOStr.getLength() - 1;
4104         SbxDimArray* pArray = new SbxDimArray(SbxBYTE);
4105         pArray->unoAddDim(lb, ub);
4106 
4107         for (sal_Int32 i = 0; i < aOStr.getLength(); ++i)
4108         {
4109             SbxVariable* pNew = new SbxVariable(SbxBYTE);
4110             pNew->PutByte(aOStr[i]);
4111             pArray->Put(pNew, i);
4112         }
4113 
4114         SbxVariable* retVar = rPar.Get(0);
4115         SbxFlagBits nFlags = retVar->GetFlags();
4116         retVar->ResetFlag(SbxFlagBits::Fixed);
4117         retVar->PutObject(pArray);
4118         retVar->SetFlags(nFlags);
4119         retVar->SetParameters(nullptr);
4120         return;
4121     }
4122 
4123     std::vector<TransliterationFlags> aTranslitSet;
4124     auto check = [&nConversion, &aTranslitSet](sal_Int32 conv, TransliterationFlags flag)
4125     {
4126         if ((nConversion & conv) != conv)
4127             return false;
4128 
4129         aTranslitSet.push_back(flag);
4130         nConversion &= ~conv;
4131         return true;
4132     };
4133 
4134     // Check mutually exclusive bits together
4135 
4136     if (!check(ooo::vba::VbStrConv::vbProperCase, TransliterationFlags::TITLE_CASE))
4137         if (!check(ooo::vba::VbStrConv::vbUpperCase, TransliterationFlags::LOWERCASE_UPPERCASE))
4138             check(ooo::vba::VbStrConv::vbLowerCase, TransliterationFlags::UPPERCASE_LOWERCASE);
4139 
4140     if (!check(ooo::vba::VbStrConv::vbWide, TransliterationFlags::HALFWIDTH_FULLWIDTH))
4141         check(ooo::vba::VbStrConv::vbNarrow, TransliterationFlags::FULLWIDTH_HALFWIDTH);
4142 
4143     if (!check(ooo::vba::VbStrConv::vbKatakana, TransliterationFlags::HIRAGANA_KATAKANA))
4144         check(ooo::vba::VbStrConv::vbHiragana, TransliterationFlags::KATAKANA_HIRAGANA);
4145 
4146     if (nConversion) // unknown / incorrectly combined bits
4147         return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
4148 
4149     OUString aStr = rPar.Get(1)->GetOUString();
4150     if (!aStr.isEmpty() && !aTranslitSet.empty())
4151     {
4152         uno::Reference< uno::XComponentContext > xContext = getProcessComponentContext();
4153 
4154         for (auto transliterationFlag : aTranslitSet)
4155         {
4156             if (transliterationFlag == TransliterationFlags::TITLE_CASE)
4157             {
4158                 // TransliterationWrapper only handles the first character of the passed string
4159                 // when handling TITLE_CASE; see Transliteration_titlecase::transliterateImpl in
4160                 // i18npool/source/transliteration/transliteration_body.cxx
4161                 CharClass aCharClass{ xContext, LanguageTag(nLanguage) };
4162                 aStr = aCharClass.titlecase(aCharClass.lowercase(aStr));
4163             }
4164             else
4165             {
4166                 utl::TransliterationWrapper aWrapper(xContext, transliterationFlag);
4167                 aStr = aWrapper.transliterate(aStr, nLanguage, 0, aStr.getLength(), nullptr);
4168             }
4169         }
4170     }
4171 
4172     rPar.Get(0)->PutString(aStr);
4173 }
4174 
4175 
4176 void SbRtl_Beep(StarBASIC *, SbxArray & rPar, bool)
4177 {
4178     if (rPar.Count() != 1)
4179     {
4180         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
4181     }
4182     Sound::Beep();
4183 }
4184 
4185 void SbRtl_Load(StarBASIC *, SbxArray & rPar, bool)
4186 {
4187     if (rPar.Count() != 2)
4188     {
4189         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
4190     }
4191 
4192 
4193     SbxBase* pObj = rPar.Get(1)->GetObject();
4194     if ( !pObj )
4195         return;
4196 
4197     if (SbUserFormModule* pModule = dynamic_cast<SbUserFormModule*>(pObj))
4198     {
4199         pModule->Load();
4200     }
4201     else if (SbxObject* pSbxObj = dynamic_cast<SbxObject*>(pObj))
4202     {
4203         SbxVariable* pVar = pSbxObj->Find("Load", SbxClassType::Method);
4204         if( pVar )
4205         {
4206             pVar->GetInteger();
4207         }
4208     }
4209 }
4210 
4211 void SbRtl_Unload(StarBASIC *, SbxArray & rPar, bool)
4212 {
4213     rPar.Get(0)->PutEmpty();
4214     if (rPar.Count() != 2)
4215     {
4216         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
4217     }
4218 
4219 
4220     SbxBase* pObj = rPar.Get(1)->GetObject();
4221     if ( !pObj )
4222         return;
4223 
4224     if (SbUserFormModule* pFormModule = dynamic_cast<SbUserFormModule*>(pObj))
4225     {
4226         pFormModule->Unload();
4227     }
4228     else if (SbxObject *pSbxObj = dynamic_cast<SbxObject*>(pObj))
4229     {
4230         SbxVariable* pVar = pSbxObj->Find("Unload", SbxClassType::Method);
4231         if( pVar )
4232         {
4233             pVar->GetInteger();
4234         }
4235     }
4236 }
4237 
4238 void SbRtl_LoadPicture(StarBASIC *, SbxArray & rPar, bool)
4239 {
4240     if (rPar.Count() != 2)
4241     {
4242         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
4243     }
4244 
4245     OUString aFileURL = getFullPath(rPar.Get(1)->GetOUString());
4246     std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream( aFileURL, StreamMode::READ ));
4247     if( pStream )
4248     {
4249         Bitmap aBmp;
4250         ReadDIB(aBmp, *pStream, true);
4251         BitmapEx aBitmapEx(aBmp);
4252         Graphic aGraphic(aBitmapEx);
4253 
4254         SbxObjectRef xRef = new SbStdPicture;
4255         static_cast<SbStdPicture*>(xRef.get())->SetGraphic( aGraphic );
4256         rPar.Get(0)->PutObject(xRef.get());
4257     }
4258 }
4259 
4260 void SbRtl_SavePicture(StarBASIC *, SbxArray & rPar, bool)
4261 {
4262     rPar.Get(0)->PutEmpty();
4263     if (rPar.Count() != 3)
4264     {
4265         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
4266     }
4267 
4268     SbxBase* pObj = rPar.Get(1)->GetObject();
4269     if (SbStdPicture *pPicture = dynamic_cast<SbStdPicture*>(pObj))
4270     {
4271         SvFileStream aOStream(rPar.Get(2)->GetOUString(), StreamMode::WRITE | StreamMode::TRUNC);
4272         const Graphic& aGraphic = pPicture->GetGraphic();
4273         TypeSerializer aSerializer(aOStream);
4274         aSerializer.writeGraphic(aGraphic);
4275     }
4276 }
4277 
4278 void SbRtl_MsgBox(StarBASIC *, SbxArray & rPar, bool)
4279 {
4280     const sal_uInt32 nArgCount = rPar.Count();
4281     if( nArgCount < 2 || nArgCount > 6 )
4282     {
4283         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
4284     }
4285 
4286     // tdf#147529 - check for missing parameters
4287     if (IsMissing(rPar, 1))
4288     {
4289         return StarBASIC::Error(ERRCODE_BASIC_NOT_OPTIONAL);
4290     }
4291 
4292     // tdf#151012 - initialize optional parameters with their default values (number of buttons)
4293     WinBits nType = static_cast<WinBits>(GetOptionalIntegerParamOrDefault(rPar, 2, 0)); // MB_OK
4294     WinBits nStyle = nType;
4295     nStyle &= 15; // delete bits 4-16
4296     if (nStyle > 5)
4297         nStyle = 0;
4298 
4299     enum BasicResponse
4300     {
4301         Ok = 1,
4302         Cancel = 2,
4303         Abort = 3,
4304         Retry = 4,
4305         Ignore = 5,
4306         Yes = 6,
4307         No = 7
4308     };
4309 
4310     OUString aMsg = rPar.Get(1)->GetOUString();
4311     // tdf#151012 - initialize optional parameters with their default values (title of dialog box)
4312     OUString aTitle = GetOptionalOUStringParamOrDefault(rPar, 3, Application::GetDisplayName());
4313 
4314     WinBits nDialogType = nType & (16+32+64);
4315 
4316     SolarMutexGuard aSolarGuard;
4317     weld::Widget* pParent = Application::GetDefDialogParent();
4318 
4319     VclMessageType eType = VclMessageType::Other;
4320 
4321     switch (nDialogType)
4322     {
4323         case 16:
4324             eType = VclMessageType::Error;
4325             break;
4326         case 32:
4327             eType = VclMessageType::Question;
4328             break;
4329         case 48:
4330             eType = VclMessageType::Warning;
4331             break;
4332         case 64:
4333             eType = VclMessageType::Info;
4334             break;
4335     }
4336 
4337     std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent,
4338                 eType, VclButtonsType::NONE, aMsg, GetpApp()));
4339 
4340     switch (nStyle)
4341     {
4342         case 0: // MB_OK
4343         default:
4344             xBox->add_button(GetStandardText(StandardButtonType::OK), BasicResponse::Ok);
4345             break;
4346         case 1: // MB_OKCANCEL
4347             xBox->add_button(GetStandardText(StandardButtonType::OK), BasicResponse::Ok);
4348             xBox->add_button(GetStandardText(StandardButtonType::Cancel), BasicResponse::Cancel);
4349 
4350             if (nType & 256 || nType & 512)
4351                 xBox->set_default_response(BasicResponse::Cancel);
4352             else
4353                 xBox->set_default_response(BasicResponse::Ok);
4354 
4355             break;
4356         case 2: // MB_ABORTRETRYIGNORE
4357             xBox->add_button(GetStandardText(StandardButtonType::Abort), BasicResponse::Abort);
4358             xBox->add_button(GetStandardText(StandardButtonType::Retry), BasicResponse::Retry);
4359             xBox->add_button(GetStandardText(StandardButtonType::Ignore), BasicResponse::Ignore);
4360 
4361             if (nType & 256)
4362                 xBox->set_default_response(BasicResponse::Retry);
4363             else if (nType & 512)
4364                 xBox->set_default_response(BasicResponse::Ignore);
4365             else
4366                 xBox->set_default_response(BasicResponse::Cancel);
4367 
4368             break;
4369         case 3: // MB_YESNOCANCEL
4370             xBox->add_button(GetStandardText(StandardButtonType::Yes), BasicResponse::Yes);
4371             xBox->add_button(GetStandardText(StandardButtonType::No), BasicResponse::No);
4372             xBox->add_button(GetStandardText(StandardButtonType::Cancel), BasicResponse::Cancel);
4373 
4374             if (nType & 256 || nType & 512)
4375                 xBox->set_default_response(BasicResponse::Cancel);
4376             else
4377                 xBox->set_default_response(BasicResponse::Yes);
4378 
4379             break;
4380         case 4: // MB_YESNO
4381             xBox->add_button(GetStandardText(StandardButtonType::Yes), BasicResponse::Yes);
4382             xBox->add_button(GetStandardText(StandardButtonType::No), BasicResponse::No);
4383 
4384             if (nType & 256 || nType & 512)
4385                 xBox->set_default_response(BasicResponse::No);
4386             else
4387                 xBox->set_default_response(BasicResponse::Yes);
4388 
4389             break;
4390         case 5: // MB_RETRYCANCEL
4391             xBox->add_button(GetStandardText(StandardButtonType::Retry), BasicResponse::Retry);
4392             xBox->add_button(GetStandardText(StandardButtonType::Cancel), BasicResponse::Cancel);
4393 
4394             if (nType & 256 || nType & 512)
4395                 xBox->set_default_response(BasicResponse::Cancel);
4396             else
4397                 xBox->set_default_response(BasicResponse::Retry);
4398 
4399             break;
4400     }
4401 
4402     xBox->set_title(aTitle);
4403     sal_Int16 nRet = xBox->run();
4404     rPar.Get(0)->PutInteger(nRet);
4405 }
4406 
4407 void SbRtl_SetAttr(StarBASIC *, SbxArray & rPar, bool)
4408 {
4409     rPar.Get(0)->PutEmpty();
4410     if (rPar.Count() == 3)
4411     {
4412         OUString aStr = rPar.Get(1)->GetOUString();
4413         SbAttributes nFlags = static_cast<SbAttributes>(rPar.Get(2)->GetInteger());
4414 
4415         if( hasUno() )
4416         {
4417             const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
4418             if( xSFI.is() )
4419             {
4420                 try
4421                 {
4422                     bool bReadOnly = bool(nFlags & SbAttributes::READONLY);
4423                     xSFI->setReadOnly( aStr, bReadOnly );
4424                     bool bHidden   = bool(nFlags & SbAttributes::HIDDEN);
4425                     xSFI->setHidden( aStr, bHidden );
4426                 }
4427                 catch(const Exception & )
4428                 {
4429                     StarBASIC::Error( ERRCODE_IO_GENERAL );
4430                 }
4431             }
4432         }
4433     }
4434     else
4435     {
4436         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
4437     }
4438 }
4439 
4440 void SbRtl_Reset(StarBASIC *, SbxArray &, bool)
4441 {
4442     SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
4443     if (pIO)
4444     {
4445         pIO->CloseAll();
4446     }
4447 }
4448 
4449 void SbRtl_DumpAllObjects(StarBASIC * pBasic, SbxArray & rPar, bool)
4450 {
4451     const sal_uInt32 nArgCount = rPar.Count();
4452     if( nArgCount < 2 || nArgCount > 3 )
4453     {
4454         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
4455     }
4456     else if( !pBasic )
4457     {
4458         StarBASIC::Error( ERRCODE_BASIC_INTERNAL_ERROR );
4459     }
4460     else
4461     {
4462         SbxObject* p = pBasic;
4463         while( p->GetParent() )
4464         {
4465             p = p->GetParent();
4466         }
4467         SvFileStream aStrm(rPar.Get(1)->GetOUString(),
4468                             StreamMode::WRITE | StreamMode::TRUNC );
4469         p->Dump(aStrm, rPar.Get(2)->GetBool());
4470         aStrm.Close();
4471         if( aStrm.GetError() != ERRCODE_NONE )
4472         {
4473             StarBASIC::Error( ERRCODE_BASIC_IO_ERROR );
4474         }
4475     }
4476 }
4477 
4478 
4479 void SbRtl_FileExists(StarBASIC *, SbxArray & rPar, bool)
4480 {
4481     if (rPar.Count() == 2)
4482     {
4483         OUString aStr = rPar.Get(1)->GetOUString();
4484         bool bExists = false;
4485 
4486         if( hasUno() )
4487         {
4488             const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
4489             if( xSFI.is() )
4490             {
4491                 try
4492                 {
4493                     bExists = xSFI->exists( aStr );
4494                 }
4495                 catch(const Exception & )
4496                 {
4497                     StarBASIC::Error( ERRCODE_IO_GENERAL );
4498                 }
4499             }
4500         }
4501         else
4502         {
4503             DirectoryItem aItem;
4504             FileBase::RC nRet = DirectoryItem::get( getFullPath( aStr ), aItem );
4505             bExists = (nRet == FileBase::E_None);
4506         }
4507         rPar.Get(0)->PutBool(bExists);
4508     }
4509     else
4510     {
4511         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
4512     }
4513 }
4514 
4515 void SbRtl_Partition(StarBASIC *, SbxArray & rPar, bool)
4516 {
4517     if (rPar.Count() != 5)
4518     {
4519         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
4520     }
4521 
4522     sal_Int32 nNumber = rPar.Get(1)->GetLong();
4523     sal_Int32 nStart = rPar.Get(2)->GetLong();
4524     sal_Int32 nStop = rPar.Get(3)->GetLong();
4525     sal_Int32 nInterval = rPar.Get(4)->GetLong();
4526 
4527     if( nStart < 0 || nStop <= nStart || nInterval < 1 )
4528     {
4529         return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
4530     }
4531 
4532     // the Partition function inserts leading spaces before lowervalue and uppervalue
4533     // so that they both have the same number of characters as the string
4534     // representation of the value (Stop + 1). This ensures that if you use the output
4535     // of the Partition function with several values of Number, the resulting text
4536     // will be handled properly during any subsequent sort operation.
4537 
4538     // calculate the maximum number of characters before lowervalue and uppervalue
4539     OUString aBeforeStart = OUString::number( nStart - 1 );
4540     OUString aAfterStop = OUString::number( nStop + 1 );
4541     sal_Int32 nLen1 = aBeforeStart.getLength();
4542     sal_Int32 nLen2 = aAfterStop.getLength();
4543     sal_Int32 nLen = nLen1 >= nLen2 ? nLen1:nLen2;
4544 
4545     OUStringBuffer aRetStr( nLen * 2 + 1);
4546     OUString aLowerValue;
4547     OUString aUpperValue;
4548     if( nNumber < nStart )
4549     {
4550         aUpperValue = aBeforeStart;
4551     }
4552     else if( nNumber > nStop )
4553     {
4554         aLowerValue = aAfterStop;
4555     }
4556     else
4557     {
4558         sal_Int32 nLowerValue = nNumber;
4559         sal_Int32 nUpperValue = nLowerValue;
4560         if( nInterval > 1 )
4561         {
4562             nLowerValue = ((( nNumber - nStart ) / nInterval ) * nInterval ) + nStart;
4563             nUpperValue = nLowerValue + nInterval - 1;
4564         }
4565         aLowerValue = OUString::number( nLowerValue );
4566         aUpperValue = OUString::number( nUpperValue );
4567     }
4568 
4569     nLen1 = aLowerValue.getLength();
4570     nLen2 = aUpperValue.getLength();
4571 
4572     if( nLen > nLen1 )
4573     {
4574         // appending the leading spaces for the lowervalue
4575         for ( sal_Int32 i= nLen - nLen1; i > 0; --i )
4576         {
4577             aRetStr.append(" ");
4578         }
4579     }
4580     aRetStr.append( aLowerValue + ":");
4581     if( nLen > nLen2 )
4582     {
4583         // appending the leading spaces for the uppervalue
4584         for ( sal_Int32 i= nLen - nLen2; i > 0; --i )
4585         {
4586             aRetStr.append(" ");
4587         }
4588     }
4589     aRetStr.append( aUpperValue );
4590     rPar.Get(0)->PutString(aRetStr.makeStringAndClear());
4591 }
4592 
4593 #endif
4594 
4595 sal_Int16 implGetDateYear( double aDate )
4596 {
4597     Date aRefDate(1899'12'30);
4598     sal_Int32 nDays = static_cast<sal_Int32>(aDate);
4599     aRefDate.AddDays( nDays );
4600     sal_Int16 nRet = aRefDate.GetYear();
4601     return nRet;
4602 }
4603 
4604 bool implDateSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay,
4605         bool bUseTwoDigitYear, SbDateCorrection eCorr, double& rdRet )
4606 {
4607     // XXX NOTE: For VBA years<0 are invalid and years in the range 0..29 and
4608     // 30..99 can not be input as they are 2-digit for 2000..2029 and
4609     // 1930..1999, VBA mode overrides bUseTwoDigitYear (as if that was always
4610     // true). For VBA years > 9999 are invalid.
4611     // For StarBASIC, if bUseTwoDigitYear==true then years in the range 0..99
4612     // can not be input as they are 2-digit for 1900..1999, years<0 are
4613     // accepted. If bUseTwoDigitYear==false then all years are accepted, but
4614     // year 0 is invalid (last day BCE -0001-12-31, first day CE 0001-01-01).
4615 #if HAVE_FEATURE_SCRIPTING
4616     if ( (nYear < 0 || 9999 < nYear) && SbiRuntime::isVBAEnabled() )
4617     {
4618         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
4619         return false;
4620     }
4621     else if ( nYear < 30 && SbiRuntime::isVBAEnabled() )
4622     {
4623         nYear += 2000;
4624     }
4625     else
4626 #endif
4627     {
4628         if ( 0 <= nYear && nYear < 100 &&
4629 #if HAVE_FEATURE_SCRIPTING
4630                                           (bUseTwoDigitYear || SbiRuntime::isVBAEnabled())
4631 #else
4632                                           bUseTwoDigitYear
4633 #endif
4634            )
4635         {
4636             nYear += 1900;
4637         }
4638     }
4639 
4640     sal_Int32 nAddMonths = 0;
4641     sal_Int32 nAddDays = 0;
4642     // Always sanitize values to set date and to use for validity detection.
4643     if (nMonth < 1 || 12 < nMonth)
4644     {
4645         sal_Int16 nM = ((nMonth < 1) ? (12 + (nMonth % 12)) : (nMonth % 12));
4646         nAddMonths = nMonth - nM;
4647         nMonth = nM;
4648     }
4649     // Day 0 would already be normalized during Date::Normalize(), include
4650     // it in negative days, also to detect non-validity. The actual day of
4651     // month is 1+(nDay-1)
4652     if (nDay < 1)
4653     {
4654         nAddDays = nDay - 1;
4655         nDay = 1;
4656     }
4657     else if (nDay > 31)
4658     {
4659         nAddDays = nDay - 31;
4660         nDay = 31;
4661     }
4662 
4663     Date aCurDate( nDay, nMonth, nYear );
4664 
4665     /* TODO: we could enable the same rollover mechanism for StarBASIC to be
4666      * compatible with VBA (just with our wider supported date range), then
4667      * documentation would need to be adapted. As is, the DateSerial() runtime
4668      * function works as dumb as documented... (except that the resulting date
4669      * is checked for validity now and not just day<=31 and month<=12).
4670      * If change wanted then simply remove overriding RollOver here and adapt
4671      * documentation.*/
4672 #if HAVE_FEATURE_SCRIPTING
4673     if (eCorr == SbDateCorrection::RollOver && !SbiRuntime::isVBAEnabled())
4674         eCorr = SbDateCorrection::None;
4675 #endif
4676 
4677     if (nYear == 0 || (eCorr == SbDateCorrection::None && (nAddMonths || nAddDays || !aCurDate.IsValidDate())))
4678     {
4679 #if HAVE_FEATURE_SCRIPTING
4680         StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
4681 #endif
4682         return false;
4683     }
4684 
4685     if (eCorr != SbDateCorrection::None)
4686     {
4687         aCurDate.Normalize();
4688         if (nAddMonths)
4689             aCurDate.AddMonths( nAddMonths);
4690         if (nAddDays)
4691             aCurDate.AddDays( nAddDays);
4692         if (eCorr == SbDateCorrection::TruncateToMonth && aCurDate.GetMonth() != nMonth)
4693         {
4694             if (aCurDate.GetYear() == SAL_MAX_INT16 && nMonth == 12)
4695             {
4696                 // Roll over and back not possible, hard max.
4697                 aCurDate.SetMonth(12);
4698                 aCurDate.SetDay(31);
4699             }
4700             else
4701             {
4702                 aCurDate.SetMonth(nMonth);
4703                 aCurDate.SetDay(1);
4704                 aCurDate.AddMonths(1);
4705                 aCurDate.AddDays(-1);
4706             }
4707         }
4708     }
4709 
4710     rdRet = GetDayDiff(aCurDate);
4711     return true;
4712 }
4713 
4714 double implTimeSerial( sal_Int16 nHours, sal_Int16 nMinutes, sal_Int16 nSeconds )
4715 {
4716     return
4717         static_cast<double>( nHours * ::tools::Time::secondPerHour +
4718                              nMinutes * ::tools::Time::secondPerMinute +
4719                              nSeconds)
4720         /
4721         static_cast<double>( ::tools::Time::secondPerDay );
4722 }
4723 
4724 bool implDateTimeSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay,
4725                          sal_Int16 nHour, sal_Int16 nMinute, sal_Int16 nSecond,
4726                          double& rdRet )
4727 {
4728     double dDate;
4729     if(!implDateSerial(nYear, nMonth, nDay, false/*bUseTwoDigitYear*/, SbDateCorrection::None, dDate))
4730         return false;
4731     rdRet += dDate + implTimeSerial(nHour, nMinute, nSecond);
4732     return true;
4733 }
4734 
4735 sal_Int16 implGetMinute( double dDate )
4736 {
4737     double nFrac = dDate - floor( dDate );
4738     nFrac *= 86400.0;
4739     sal_Int32 nSeconds = static_cast<sal_Int32>(nFrac + 0.5);
4740     sal_Int16 nTemp = static_cast<sal_Int16>(nSeconds % 3600);
4741     sal_Int16 nMin = nTemp / 60;
4742     return nMin;
4743 }
4744 
4745 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
4746