/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <config_features.h>
#include <sal/config.h>
#include <config_version.h>
#include <cstddef>
#include <rtl/math.hxx>
#include <vcl/svapp.hxx>
#include <vcl/mapmod.hxx>
#include <vcl/outdev.hxx>
#include <vcl/timer.hxx>
#include <vcl/settings.hxx>
#include <basic/sbxvar.hxx>
#include <basic/sbx.hxx>
#include <svl/zforlist.hxx>
#include <tools/urlobj.hxx>
#include <tools/fract.hxx>
#include <o3tl/temporary.hxx>
#include <osl/file.hxx>
#include <osl/process.h>
#include <sbobjmod.hxx>
#include <basic/sbuno.hxx>
#include <date.hxx>
#include <sbintern.hxx>
#include <runtime.hxx>
#include <rtlproto.hxx>
#include "dllmgr.hxx"
#include <iosys.hxx>
#include <sbunoobj.hxx>
#include <propacc.hxx>
#include <sal/log.hxx>
#include <eventatt.hxx>
#include <rtl/math.h>
#include <svl/numformat.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/string.hxx>
#include <com/sun/star/uno/Sequence.hxx>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/i18n/LocaleCalendar2.hpp>
#include <com/sun/star/sheet/XFunctionAccess.hpp>
#include <memory>
using namespace comphelper;
using namespace com::sun::star::i18n;
using namespace com::sun::star::lang;
using namespace com::sun::star::sheet;
using namespace com::sun::star::uno;
static Reference< XCalendar4 > const & getLocaleCalendar()
{
static Reference< XCalendar4 > xCalendar = LocaleCalendar2::create(getProcessComponentContext());
static css::lang::Locale aLastLocale;
static bool bNeedsReload = true ;
css::lang::Locale aLocale = Application::GetSettings().GetLanguageTag().getLocale();
bNeedsReload = bNeedsReload ||
( aLocale.Language != aLastLocale.Language ||
aLocale.Country != aLastLocale.Country ||
aLocale.Variant != aLastLocale.Variant );
if ( bNeedsReload )
{
bNeedsReload = false ;
aLastLocale = aLocale;
xCalendar->loadDefaultCalendar( aLocale );
}
return xCalendar;
}
#if HAVE_FEATURE_SCRIPTING
void SbRtl_CallByName(StarBASIC *, SbxArray & rPar, bool )
{
const sal_Int16 vbGet = 2;
const sal_Int16 vbLet = 4;
const sal_Int16 vbMethod = 1;
const sal_Int16 vbSet = 8;
// At least 3 parameter needed plus function itself -> 4
sal_uInt32 nParCount = rPar.Count();
if ( nParCount < 4 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
// 1. parameter is object
SbxBase* pObjVar = rPar.Get(1)->GetObject();
SbxObject* pObj = nullptr;
if ( pObjVar )
pObj = dynamic_cast <SbxObject*>( pObjVar );
if ( !pObj )
if (auto pSbxVar = dynamic_cast <const SbxVariable*>( pObjVar))
pObj = dynamic_cast <SbxObject*>( pSbxVar->GetObject() );
if ( !pObj )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_PARAMETER );
return ;
}
// 2. parameter is ProcName
OUString aNameStr = rPar.Get(2)->GetOUString();
// 3. parameter is CallType
sal_Int16 nCallType = rPar.Get(3)->GetInteger();
//SbxObject* pFindObj = NULL;
SbxVariable* pFindVar = pObj->Find( aNameStr, SbxClassType::DontCare );
if ( pFindVar == nullptr )
{
StarBASIC::Error( ERRCODE_BASIC_PROC_UNDEFINED );
return ;
}
switch ( nCallType )
{
case vbGet:
{
SbxValues aVals;
aVals.eType = SbxVARIANT;
pFindVar->Get( aVals );
SbxVariableRef refVar = rPar.Get(0);
refVar->Put( aVals );
}
break ;
case vbLet:
case vbSet:
{
if ( nParCount != 5 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
SbxVariableRef pValVar = rPar.Get(4);
if ( nCallType == vbLet )
{
SbxValues aVals;
aVals.eType = SbxVARIANT;
pValVar->Get( aVals );
pFindVar->Put( aVals );
}
else
{
SbxVariableRef rFindVar = pFindVar;
SbiInstance* pInst = GetSbData()->pInst;
SbiRuntime* pRT = pInst ? pInst->pRun : nullptr;
if ( pRT != nullptr )
{
pRT->StepSET_Impl( pValVar, rFindVar );
}
}
}
break ;
case vbMethod:
{
SbMethod* pMeth = dynamic_cast <SbMethod*>( pFindVar );
if ( pMeth == nullptr )
{
StarBASIC::Error( ERRCODE_BASIC_PROC_UNDEFINED );
return ;
}
// Setup parameters
SbxArrayRef xArray;
sal_uInt32 nMethParamCount = nParCount - 4;
if ( nMethParamCount > 0 )
{
xArray = new SbxArray;
for ( sal_uInt32 i = 0 ; i < nMethParamCount ; i++ )
{
SbxVariable* pPar = rPar.Get(i + 4);
xArray->Put(pPar, i + 1);
}
}
// Call method
SbxVariableRef refVar = rPar.Get(0);
if ( xArray.is() )
pMeth->SetParameters( xArray.get() );
pMeth->Call( refVar.get() );
pMeth->SetParameters( nullptr );
}
break ;
default :
StarBASIC::Error( ERRCODE_BASIC_PROC_UNDEFINED );
}
}
void SbRtl_CBool(StarBASIC *, SbxArray & rPar, bool ) // JSM
{
bool bVal = false ;
if (rPar.Count() == 2)
{
SbxVariable* pSbxVariable = rPar.Get(1);
bVal = pSbxVariable->GetBool();
}
else
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
}
rPar.Get(0)->PutBool(bVal);
}
void SbRtl_CByte(StarBASIC *, SbxArray & rPar, bool ) // JSM
{
sal_uInt8 nByte = 0;
if (rPar.Count() == 2)
{
SbxVariable* pSbxVariable = rPar.Get(1);
nByte = pSbxVariable->GetByte();
}
else
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
}
rPar.Get(0)->PutByte(nByte);
}
void SbRtl_CCur(StarBASIC *, SbxArray & rPar, bool )
{
sal_Int64 nCur = 0;
if (rPar.Count() == 2)
{
SbxVariable* pSbxVariable = rPar.Get(1);
nCur = pSbxVariable->GetCurrency();
}
else
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
}
rPar.Get(0)->PutCurrency(nCur);
}
void SbRtl_CDec(StarBASIC *, SbxArray & rPar, bool )
{
#ifdef _WIN32
SbxDecimal* pDec = nullptr;
if (rPar.Count() == 2)
{
SbxVariable* pSbxVariable = rPar.Get(1);
pDec = pSbxVariable->GetDecimal();
}
else
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
}
rPar.Get(0)->PutDecimal(pDec);
#else
rPar.Get(0)->PutEmpty();
StarBASIC::Error(ERRCODE_BASIC_NOT_IMPLEMENTED);
#endif
}
void SbRtl_CDate(StarBASIC *, SbxArray & rPar, bool ) // JSM
{
double nVal = 0.0;
if (rPar.Count() != 2)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
SbxVariable* pSbxVariable = rPar.Get(1);
nVal = pSbxVariable->GetDate();
rPar.Get(0)->PutDate(nVal);
}
void SbRtl_CDbl(StarBASIC *, SbxArray & rPar, bool ) // JSM
{
double nVal = 0.0;
if (rPar.Count() == 2)
{
SbxVariable* pSbxVariable = rPar.Get(1);
if ( pSbxVariable->GetType() == SbxSTRING )
{
// #41690
OUString aScanStr = pSbxVariable->GetOUString();
ErrCode Error = SbxValue::ScanNumIntnl( aScanStr, nVal );
if ( Error != ERRCODE_NONE )
{
StarBASIC::Error( Error );
}
}
else
{
nVal = pSbxVariable->GetDouble();
}
}
else
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
}
rPar.Get(0)->PutDouble(nVal);
}
void SbRtl_CInt(StarBASIC *, SbxArray & rPar, bool ) // JSM
{
sal_Int16 nVal = 0;
if (rPar.Count() != 2)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
SbxVariable* pSbxVariable = rPar.Get(1);
nVal = pSbxVariable->GetInteger();
rPar.Get(0)->PutInteger(nVal);
}
void SbRtl_CLng(StarBASIC *, SbxArray & rPar, bool ) // JSM
{
sal_Int32 nVal = 0;
if (rPar.Count() != 2)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
SbxVariable* pSbxVariable = rPar.Get(1);
nVal = pSbxVariable->GetLong();
rPar.Get(0)->PutLong(nVal);
}
void SbRtl_CSng(StarBASIC *, SbxArray & rPar, bool ) // JSM
{
float nVal = float (0.0);
if (rPar.Count() == 2)
{
SbxVariable* pSbxVariable = rPar.Get(1);
if ( pSbxVariable->GetType() == SbxSTRING )
{
// #41690
double dVal = 0.0;
OUString aScanStr = pSbxVariable->GetOUString();
ErrCode Error = SbxValue::ScanNumIntnl( aScanStr, dVal, /*bSingle=*/true );
if ( SbxBase::GetError() == ERRCODE_NONE && Error != ERRCODE_NONE )
{
StarBASIC::Error( Error );
}
nVal = static_cast <float >(dVal);
}
else
{
nVal = pSbxVariable->GetSingle();
}
}
else
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
}
rPar.Get(0)->PutSingle(nVal);
}
void SbRtl_CStr(StarBASIC *, SbxArray & rPar, bool ) // JSM
{
if (rPar.Count() != 2)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
SbxVariable* pSbxVariable = rPar.Get(1);
OUString aString = pSbxVariable->GetOUString();
rPar.Get(0)->PutString(aString);
}
void SbRtl_CVar(StarBASIC *, SbxArray & rPar, bool ) // JSM
{
SbxValues aVals( SbxVARIANT );
if (rPar.Count() == 2)
{
SbxVariable* pSbxVariable = rPar.Get(1);
pSbxVariable->Get( aVals );
}
else
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
}
rPar.Get(0)->Put(aVals);
}
void SbRtl_CVErr(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 2)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
SbxVariable* pSbxVariable = rPar.Get(1);
sal_Int16 nErrCode = pSbxVariable->GetInteger();
rPar.Get(0)->PutErr(nErrCode);
}
void SbRtl_Iif(StarBASIC *, SbxArray & rPar, bool ) // JSM
{
if (rPar.Count() != 4)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
if (rPar.Get(1)->GetBool())
{
*rPar.Get(0) = *rPar.Get(2);
}
else
{
*rPar.Get(0) = *rPar.Get(3);
}
}
void SbRtl_GetSystemType(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 1)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
// Removed for SRC595
rPar.Get(0)->PutInteger(-1);
}
void SbRtl_GetGUIType(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 1)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
// 17.7.2000 Make simple solution for testtool / fat office
#if defined (_WIN32)
rPar.Get(0)->PutInteger(1);
#elif defined (UNX)
rPar.Get(0)->PutInteger(4);
#else
rPar.Get(0)->PutInteger(-1);
#endif
}
void SbRtl_Red(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 2)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
sal_Int32 nRGB = rPar.Get(1)->GetLong();
nRGB &= 0x00FF0000;
nRGB >>= 16;
rPar.Get(0)->PutInteger(static_cast <sal_Int16>(nRGB));
}
void SbRtl_Green(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 2)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
sal_Int32 nRGB = rPar.Get(1)->GetLong();
nRGB &= 0x0000FF00;
nRGB >>= 8;
rPar.Get(0)->PutInteger(static_cast <sal_Int16>(nRGB));
}
void SbRtl_Blue(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 2)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
sal_Int32 nRGB = rPar.Get(1)->GetLong();
nRGB &= 0x000000FF;
rPar.Get(0)->PutInteger(static_cast <sal_Int16>(nRGB));
}
void SbRtl_Switch(StarBASIC *, SbxArray & rPar, bool )
{
sal_uInt32 nCount = rPar.Count();
if ( !(nCount & 0x0001 ))
{
// number of arguments must be odd
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
}
sal_uInt32 nCurExpr = 1;
while ( nCurExpr < (nCount-1) )
{
if (rPar.Get(nCurExpr)->GetBool())
{
(*rPar.Get(0)) = *(rPar.Get(nCurExpr + 1));
return ;
}
nCurExpr += 2;
}
rPar.Get(0)->PutNull();
}
//i#64882# Common wait impl for existing Wait and new WaitUntil
// rtl functions
void Wait_Impl( bool bDurationBased, SbxArray& rPar )
{
if (rPar.Count() != 2)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
tools::Long nWait = 0;
if ( bDurationBased )
{
double dWait = rPar.Get(1)->GetDouble();
double dNow = Now_Impl();
double dSecs = ( dWait - dNow ) * 24.0 * 3600.0;
nWait = static_cast <tools::Long >( dSecs * 1000 ); // wait in thousands of sec
}
else
{
nWait = rPar.Get(1)->GetLong();
}
if ( nWait < 0 )
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
Timer aTimer("basic Wait_Impl" );
aTimer.SetTimeout( nWait );
aTimer.Start();
while ( aTimer.IsActive() && !Application::IsQuit())
{
Application::Yield();
}
}
//i#64882#
void SbRtl_Wait(StarBASIC *, SbxArray & rPar, bool )
{
Wait_Impl( false , rPar );
}
//i#64882# add new WaitUntil ( for application.wait )
// share wait_impl with 'normal' oobasic wait
void SbRtl_WaitUntil(StarBASIC *, SbxArray & rPar, bool )
{
Wait_Impl( true , rPar );
}
void SbRtl_DoEvents(StarBASIC *, SbxArray & rPar, bool )
{
// don't understand what upstream are up to
// we already process application events etc. in between
// basic runtime pcode ( on a timed basis )
// always return 0
rPar.Get(0)->PutInteger(0);
Application::Reschedule( true );
}
void SbRtl_GetGUIVersion(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 1)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
// Removed for SRC595
rPar.Get(0)->PutLong(-1);
}
void SbRtl_Choose(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() < 2)
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
}
sal_Int16 nIndex = rPar.Get(1)->GetInteger();
sal_uInt32 nCount = rPar.Count();
nCount--;
if ( nCount == 1 || nIndex > sal::static_int_cast<sal_Int16>(nCount-1) || nIndex < 1 )
{
rPar.Get(0)->PutNull();
return ;
}
(*rPar.Get(0)) = *(rPar.Get(nIndex + 1));
}
void SbRtl_Trim(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() < 2)
{
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
}
OUString aStr(comphelper::string::strip(rPar.Get(1)->GetOUString(), ' ' ));
rPar.Get(0)->PutString(aStr);
}
void SbRtl_GetSolarVersion(StarBASIC *, SbxArray & rPar, bool )
{
rPar.Get(0)->PutLong(LIBO_VERSION_MAJOR * 10000 + LIBO_VERSION_MINOR * 100
+ LIBO_VERSION_MICRO * 1);
}
void SbRtl_TwipsPerPixelX(StarBASIC *, SbxArray & rPar, bool )
{
sal_Int32 nResult = 0;
Size aSize( 100,0 );
MapMode aMap( MapUnit::MapTwip );
OutputDevice* pDevice = Application::GetDefaultDevice();
if ( pDevice )
{
aSize = pDevice->PixelToLogic( aSize, aMap );
nResult = aSize.Width() / 100;
}
rPar.Get(0)->PutLong(nResult);
}
void SbRtl_TwipsPerPixelY(StarBASIC *, SbxArray & rPar, bool )
{
sal_Int32 nResult = 0;
Size aSize( 0,100 );
MapMode aMap( MapUnit::MapTwip );
OutputDevice* pDevice = Application::GetDefaultDevice();
if ( pDevice )
{
aSize = pDevice->PixelToLogic( aSize, aMap );
nResult = aSize.Height() / 100;
}
rPar.Get(0)->PutLong(nResult);
}
void SbRtl_FreeLibrary(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 2)
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
}
GetSbData()->pInst->GetDllMgr()->FreeDll(rPar.Get(1)->GetOUString());
}
bool IsBaseIndexOne()
{
bool bResult = false ;
if ( GetSbData()->pInst && GetSbData()->pInst->pRun )
{
sal_uInt16 res = GetSbData()->pInst->pRun->GetBase();
if ( res )
{
bResult = true ;
}
}
return bResult;
}
void SbRtl_Array(StarBASIC *, SbxArray & rPar, bool )
{
SbxDimArray* pArray = new SbxDimArray( SbxVARIANT );
sal_uInt32 nArraySize = rPar.Count() - 1;
bool bIncIndex = IsBaseIndexOne();
if ( nArraySize )
{
if ( bIncIndex )
{
pArray->AddDim(1, sal::static_int_cast<sal_Int32>(nArraySize));
}
else
{
pArray->AddDim(0, sal::static_int_cast<sal_Int32>(nArraySize) - 1);
}
}
else
{
pArray->unoAddDim(0, -1);
}
// insert parameters into the array
for ( sal_uInt32 i = 0 ; i < nArraySize ; i++ )
{
SbxVariable* pVar = rPar.Get(i + 1);
SbxVariable* pNew = new SbxEnsureParentVariable(*pVar);
pNew->SetFlag( SbxFlagBits::Write );
sal_Int32 aIdx[1];
aIdx[0] = static_cast <sal_Int32>(i);
if ( bIncIndex )
{
++aIdx[0];
}
pArray->Put(pNew, aIdx);
}
// return array
SbxVariableRef refVar = rPar.Get(0);
SbxFlagBits nFlags = refVar->GetFlags();
refVar->ResetFlag( SbxFlagBits::Fixed );
refVar->PutObject( pArray );
refVar->SetFlags( nFlags );
refVar->SetParameters( nullptr );
}
// Featurewish #57868
// The function returns a variant-array; if there are no parameters passed,
// an empty array is created (according to dim a(); equal to a sequence of
// the length 0 in Uno).
// If there are parameters passed, there's a dimension created for each of
// them; DimArray( 2, 2, 4 ) is equal to DIM a( 2, 2, 4 )
// the array is always of the type variant
void SbRtl_DimArray(StarBASIC *, SbxArray & rPar, bool )
{
SbxDimArray * pArray = new SbxDimArray( SbxVARIANT );
sal_uInt32 nArrayDims = rPar.Count() - 1;
if ( nArrayDims > 0 )
{
for ( sal_uInt32 i = 0; i < nArrayDims ; i++ )
{
sal_Int32 ub = rPar.Get(i + 1)->GetLong();
if ( ub < 0 )
{
StarBASIC::Error( ERRCODE_BASIC_OUT_OF_RANGE );
ub = 0;
}
pArray->AddDim(0, ub);
}
}
else
{
pArray->unoAddDim(0, -1);
}
SbxVariableRef refVar = rPar.Get(0);
SbxFlagBits nFlags = refVar->GetFlags();
refVar->ResetFlag( SbxFlagBits::Fixed );
refVar->PutObject( pArray );
refVar->SetFlags( nFlags );
refVar->SetParameters( nullptr );
}
/*
* FindObject and FindPropertyObject make it possible to
* address objects and properties of the type Object with
* their name as string-parameters at the runtime.
*
* Example:
* MyObj.Prop1.Bla = 5
*
* is equal to:
* dim ObjVar as Object
* dim ObjProp as Object
* ObjName$ = "MyObj"
* ObjVar = FindObject( ObjName$ )
* PropName$ = "Prop1"
* ObjProp = FindPropertyObject( ObjVar, PropName$ )
* ObjProp.Bla = 5
*
* The names can be created dynamically at the runtime
* so that e. g. via controls "TextEdit1" to "TextEdit5"
* can be iterated in a dialog in a loop.
*/
// 1st parameter = the object's name as string
void SbRtl_FindObject(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 2)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
OUString aNameStr = rPar.Get(1)->GetOUString();
SbxBase* pFind = StarBASIC::FindSBXInCurrentScope( aNameStr );
SbxObject* pFindObj = nullptr;
if ( pFind )
{
pFindObj = dynamic_cast <SbxObject*>( pFind );
}
SbxVariableRef refVar = rPar.Get(0);
refVar->PutObject( pFindObj );
}
// address object-property in an object
// 1st parameter = object
// 2nd parameter = the property's name as string
void SbRtl_FindPropertyObject(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 3)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
SbxBase* pObjVar = rPar.Get(1)->GetObject();
SbxObject* pObj = nullptr;
if ( pObjVar )
{
pObj = dynamic_cast <SbxObject*>( pObjVar );
}
if ( !pObj )
if (auto pSbxVar = dynamic_cast <const SbxVariable*>( pObjVar))
pObj = dynamic_cast <SbxObject*>( pSbxVar->GetObject() );
OUString aNameStr = rPar.Get(2)->GetOUString();
SbxObject* pFindObj = nullptr;
if ( pObj )
{
SbxVariable* pFindVar = pObj->Find( aNameStr, SbxClassType::Object );
pFindObj = dynamic_cast <SbxObject*>( pFindVar );
}
else
{
StarBASIC::Error( ERRCODE_BASIC_BAD_PARAMETER );
}
SbxVariableRef refVar = rPar.Get(0);
refVar->PutObject( pFindObj );
}
static bool lcl_WriteSbxVariable( const SbxVariable& rVar, SvStream* pStrm,
bool bBinary, short nBlockLen, bool bIsArray )
{
sal_uInt64 const nFPos = pStrm->Tell();
bool bIsVariant = !rVar.IsFixed();
SbxDataType eType = rVar.GetType();
switch ( eType )
{
case SbxBOOL:
case SbxCHAR:
case SbxBYTE:
if ( bIsVariant )
{
pStrm->WriteUInt16( SbxBYTE ); // VarType Id
}
pStrm->WriteUChar( rVar.GetByte() );
break ;
case SbxEMPTY:
case SbxNULL:
case SbxVOID:
case SbxINTEGER:
case SbxUSHORT:
case SbxINT:
case SbxUINT:
if ( bIsVariant )
{
pStrm->WriteUInt16( SbxINTEGER ); // VarType Id
}
pStrm->WriteInt16( rVar.GetInteger() );
break ;
case SbxLONG:
case SbxULONG:
if ( bIsVariant )
{
pStrm->WriteUInt16( SbxLONG ); // VarType Id
}
pStrm->WriteInt32( rVar.GetLong() );
break ;
case SbxSALINT64:
case SbxSALUINT64:
if ( bIsVariant )
{
pStrm->WriteUInt16( SbxSALINT64 ); // VarType Id
}
pStrm->WriteUInt64( rVar.GetInt64() );
break ;
case SbxSINGLE:
if ( bIsVariant )
{
pStrm->WriteUInt16( eType ); // VarType Id
}
pStrm->WriteFloat( rVar.GetSingle() );
break ;
case SbxDOUBLE:
case SbxCURRENCY:
case SbxDATE:
if ( bIsVariant )
{
pStrm->WriteUInt16( eType ); // VarType Id
}
pStrm->WriteDouble( rVar.GetDouble() );
break ;
case SbxSTRING:
case SbxLPSTR:
{
const OUString aStr = rVar.GetOUString();
if ( !bBinary || bIsArray )
{
if ( bIsVariant )
{
pStrm->WriteUInt16( SbxSTRING );
}
pStrm->WriteUniOrByteString( aStr, osl_getThreadTextEncoding() );
}
else
{
// without any length information! without end-identifier!
// What does that mean for Unicode?! Choosing conversion to ByteString...
OString aByteStr(OUStringToOString(aStr, osl_getThreadTextEncoding()));
pStrm->WriteOString( aByteStr );
}
}
break ;
default :
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return false ;
}
if ( nBlockLen )
{
pStrm->Seek( nFPos + nBlockLen );
}
return pStrm->GetErrorCode() == ERRCODE_NONE;
}
static bool lcl_ReadSbxVariable( SbxVariable& rVar, SvStream* pStrm,
bool bBinary, short nBlockLen )
{
double aDouble;
sal_uInt64 const nFPos = pStrm->Tell();
bool bIsVariant = !rVar.IsFixed();
SbxDataType eVarType = rVar.GetType();
SbxDataType eSrcType = eVarType;
if ( bIsVariant )
{
sal_uInt16 nTemp;
pStrm->ReadUInt16( nTemp );
eSrcType = static_cast <SbxDataType>(nTemp);
}
switch ( eSrcType )
{
case SbxBOOL:
case SbxCHAR:
case SbxBYTE:
{
sal_uInt8 aByte;
pStrm->ReadUChar( aByte );
if ( bBinary && SbiRuntime::isVBAEnabled() && aByte == 1 && pStrm->eof() )
{
aByte = 0;
}
rVar.PutByte( aByte );
}
break ;
case SbxEMPTY:
case SbxNULL:
case SbxVOID:
case SbxINTEGER:
case SbxUSHORT:
case SbxINT:
case SbxUINT:
{
sal_Int16 aInt;
pStrm->ReadInt16( aInt );
rVar.PutInteger( aInt );
}
break ;
case SbxLONG:
case SbxULONG:
{
sal_Int32 aInt;
pStrm->ReadInt32( aInt );
rVar.PutLong( aInt );
}
break ;
case SbxSALINT64:
case SbxSALUINT64:
{
sal_uInt32 aInt;
pStrm->ReadUInt32( aInt );
rVar.PutInt64( static_cast <sal_Int64>(aInt) );
}
break ;
case SbxSINGLE:
{
float nS;
pStrm->ReadFloat( nS );
rVar.PutSingle( nS );
}
break ;
case SbxDOUBLE:
case SbxCURRENCY:
{
pStrm->ReadDouble( aDouble );
rVar.PutDouble( aDouble );
}
break ;
case SbxDATE:
{
pStrm->ReadDouble( aDouble );
rVar.PutDate( aDouble );
}
break ;
case SbxSTRING:
case SbxLPSTR:
{
OUString aStr = pStrm->ReadUniOrByteString(osl_getThreadTextEncoding());
rVar.PutString( aStr );
}
break ;
default :
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return false ;
}
if ( nBlockLen )
{
pStrm->Seek( nFPos + nBlockLen );
}
return pStrm->GetErrorCode() == ERRCODE_NONE;
}
// nCurDim = 1...n
static bool lcl_WriteReadSbxArray( SbxDimArray& rArr, SvStream* pStrm,
bool bBinary, sal_Int32 nCurDim, sal_Int32* pOtherDims, bool bWrite )
{
SAL_WARN_IF( nCurDim <= 0,"basic" , "Bad Dim" );
sal_Int32 nLower, nUpper;
if (!rArr.GetDim(nCurDim, nLower, nUpper))
return false ;
for (sal_Int32 nCur = nLower; nCur <= nUpper; nCur++ )
{
pOtherDims[ nCurDim-1 ] = nCur;
if ( nCurDim != 1 )
lcl_WriteReadSbxArray(rArr, pStrm, bBinary, nCurDim-1, pOtherDims, bWrite);
else
{
SbxVariable* pVar = rArr.Get(pOtherDims);
bool bRet;
if ( bWrite )
bRet = lcl_WriteSbxVariable(*pVar, pStrm, bBinary, 0, true );
else
bRet = lcl_ReadSbxVariable(*pVar, pStrm, bBinary, 0 );
if ( !bRet )
return false ;
}
}
return true ;
}
static void PutGet( SbxArray& rPar, bool bPut )
{
if (rPar.Count() != 4)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
sal_Int16 nFileNo = rPar.Get(1)->GetInteger();
SbxVariable* pVar2 = rPar.Get(2);
SbxDataType eType2 = pVar2->GetType();
bool bHasRecordNo = (eType2 != SbxEMPTY && eType2 != SbxERROR);
tools::Long nRecordNo = pVar2->GetLong();
if ( nFileNo < 1 || ( bHasRecordNo && nRecordNo < 1 ) )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
nRecordNo--;
SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
SbiStream* pSbStrm = pIO->GetStream( nFileNo );
if ( !pSbStrm || !(pSbStrm->GetMode() & (SbiStreamFlags::Binary | SbiStreamFlags::Random)) )
return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
SvStream* pStrm = pSbStrm->GetStrm();
bool bRandom = pSbStrm->IsRandom();
short nBlockLen = bRandom ? pSbStrm->GetBlockLen() : 0;
if ( bPut )
{
pSbStrm->ExpandFile();
}
if ( bHasRecordNo )
{
sal_uInt64 const nFilePos = bRandom
? static_cast <sal_uInt64>(nBlockLen * nRecordNo)
: static_cast <sal_uInt64>(nRecordNo);
pStrm->Seek( nFilePos );
}
SbxDimArray* pArr = nullptr;
SbxVariable* pVar = rPar.Get(3);
if ( pVar->GetType() & SbxARRAY )
{
SbxBase* pParObj = pVar->GetObject();
pArr = dynamic_cast <SbxDimArray*>( pParObj );
}
bool bRet;
if ( pArr )
{
sal_uInt64 const nFPos = pStrm->Tell();
sal_Int32 nDims = pArr->GetDims();
std::unique_ptr<sal_Int32[]> pDims(new sal_Int32[ nDims ]);
bRet = lcl_WriteReadSbxArray(*pArr,pStrm,!bRandom,nDims,pDims.get(),bPut);
pDims.reset();
if ( nBlockLen )
pStrm->Seek( nFPos + nBlockLen );
}
else
{
if ( bPut )
bRet = lcl_WriteSbxVariable(*pVar, pStrm, !bRandom, nBlockLen, false );
else
bRet = lcl_ReadSbxVariable(*pVar, pStrm, !bRandom, nBlockLen);
}
if ( !bRet || pStrm->GetErrorCode() )
StarBASIC::Error( ERRCODE_BASIC_IO_ERROR );
}
void SbRtl_Put(StarBASIC *, SbxArray & rPar, bool )
{
PutGet( rPar, true );
}
void SbRtl_Get(StarBASIC *, SbxArray & rPar, bool )
{
PutGet( rPar, false );
}
void SbRtl_Environ(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 2)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
OUString aResult;
osl_getEnvironment(rPar.Get(1)->GetOUString().pData, &aResult.pData);
rPar.Get(0)->PutString(aResult);
}
static double GetDialogZoomFactor( bool bX, tools::Long nValue )
{
OutputDevice* pDevice = Application::GetDefaultDevice();
double nResult = 0;
if ( pDevice )
{
Size aRefSize( nValue, nValue );
Fraction aFracX( 1, 26 );
Fraction aFracY( 1, 24 );
MapMode aMap( MapUnit::MapAppFont, Point(), aFracX, aFracY );
Size aScaledSize = pDevice->LogicToPixel( aRefSize, aMap );
aRefSize = pDevice->LogicToPixel( aRefSize, MapMode(MapUnit::MapTwip) );
double nRef, nScaled;
if ( bX )
{
nRef = aRefSize.Width();
nScaled = aScaledSize.Width();
}
else
{
nRef = aRefSize.Height();
nScaled = aScaledSize.Height();
}
nResult = nScaled / nRef;
}
return nResult;
}
void SbRtl_GetDialogZoomFactorX(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 2)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
rPar.Get(0)->PutDouble(GetDialogZoomFactor(true , rPar.Get(1)->GetLong()));
}
void SbRtl_GetDialogZoomFactorY(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 2)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
rPar.Get(0)->PutDouble(GetDialogZoomFactor(false , rPar.Get(1)->GetLong()));
}
void SbRtl_EnableReschedule(StarBASIC *, SbxArray & rPar, bool )
{
rPar.Get(0)->PutEmpty();
if (rPar.Count() != 2)
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
if ( GetSbData()->pInst )
GetSbData()->pInst->EnableReschedule(rPar.Get(1)->GetBool());
}
void SbRtl_GetSystemTicks(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 1)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
rPar.Get(0)->PutLong(tools::Time::GetSystemTicks());
}
void SbRtl_GetPathSeparator(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 1)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
rPar.Get(0)->PutString(OUString(SAL_PATHDELIMITER));
}
void SbRtl_ResolvePath(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 2)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
OUString aStr = rPar.Get(1)->GetOUString();
rPar.Get(0)->PutString(aStr);
}
void SbRtl_TypeLen(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 2)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
SbxDataType eType = rPar.Get(1)->GetType();
sal_Int16 nLen = 0;
switch ( eType )
{
case SbxEMPTY:
case SbxNULL:
case SbxVECTOR:
case SbxARRAY:
case SbxBYREF:
case SbxVOID:
case SbxHRESULT:
case SbxPOINTER:
case SbxDIMARRAY:
case SbxCARRAY:
case SbxUSERDEF:
case SbxOBJECT:
case SbxVARIANT:
case SbxDATAOBJECT:
nLen = 0;
break ;
case SbxINTEGER:
case SbxERROR:
case SbxUSHORT:
case SbxINT:
case SbxUINT:
nLen = 2;
break ;
case SbxLONG:
case SbxSINGLE:
case SbxULONG:
nLen = 4;
break ;
case SbxDOUBLE:
case SbxCURRENCY:
case SbxDATE:
case SbxSALINT64:
case SbxSALUINT64:
nLen = 8;
break ;
case SbxCHAR:
case SbxBYTE:
case SbxBOOL:
nLen = 1;
break ;
case SbxLPSTR:
case SbxLPWSTR:
case SbxCoreSTRING:
case SbxSTRING:
nLen = static_cast <sal_Int16>(rPar.Get(1)->GetOUString().getLength());
break ;
default :
nLen = 0;
break ;
}
rPar.Get(0)->PutInteger(nLen);
}
// 1st parameter == class name, other parameters for initialisation
void SbRtl_CreateUnoStruct(StarBASIC *, SbxArray & rPar, bool )
{
RTL_Impl_CreateUnoStruct( rPar );
}
// 1st parameter == service-name
void SbRtl_CreateUnoService(StarBASIC *, SbxArray & rPar, bool )
{
RTL_Impl_CreateUnoService( rPar );
}
void SbRtl_CreateUnoServiceWithArguments(StarBASIC *, SbxArray & rPar, bool )
{
RTL_Impl_CreateUnoServiceWithArguments( rPar );
}
void SbRtl_CreateUnoValue(StarBASIC *, SbxArray & rPar, bool )
{
RTL_Impl_CreateUnoValue( rPar );
}
// no parameters
void SbRtl_GetProcessServiceManager(StarBASIC *, SbxArray & rPar, bool )
{
RTL_Impl_GetProcessServiceManager( rPar );
}
// 1st parameter == Sequence<PropertyValue>
void SbRtl_CreatePropertySet(StarBASIC *, SbxArray & rPar, bool )
{
RTL_Impl_CreatePropertySet( rPar );
}
// multiple interface-names as parameters
void SbRtl_HasUnoInterfaces(StarBASIC *, SbxArray & rPar, bool )
{
RTL_Impl_HasInterfaces( rPar );
}
void SbRtl_IsUnoStruct(StarBASIC *, SbxArray & rPar, bool )
{
RTL_Impl_IsUnoStruct( rPar );
}
void SbRtl_EqualUnoObjects(StarBASIC *, SbxArray & rPar, bool )
{
RTL_Impl_EqualUnoObjects( rPar );
}
void SbRtl_CreateUnoDialog(StarBASIC *, SbxArray & rPar, bool )
{
RTL_Impl_CreateUnoDialog( rPar );
}
// Return the application standard lib as root scope
void SbRtl_GlobalScope(StarBASIC * pBasic, SbxArray & rPar, bool )
{
SbxObject* p = pBasic;
while ( p->GetParent() )
{
p = p->GetParent();
}
SbxVariableRef refVar = rPar.Get(0);
refVar->PutObject( p );
}
// Helper functions to convert Url from/to system paths
void SbRtl_ConvertToUrl(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 2)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
OUString aStr = rPar.Get(1)->GetOUString();
INetURLObject aURLObj( aStr, INetProtocol::File );
OUString aFileURL = aURLObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
if ( aFileURL.isEmpty() )
{
osl::File::getFileURLFromSystemPath(aStr, aFileURL);
}
if ( aFileURL.isEmpty() )
{
aFileURL = aStr;
}
rPar.Get(0)->PutString(aFileURL);
}
void SbRtl_ConvertFromUrl(StarBASIC *, SbxArray & rPar, bool )
{
if (rPar.Count() != 2)
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
OUString aStr = rPar.Get(1)->GetOUString();
OUString aSysPath;
::osl::File::getSystemPathFromFileURL( aStr, aSysPath );
if ( aSysPath.isEmpty() )
{
aSysPath = aStr;
}
rPar.Get(0)->PutString(aSysPath);
}
// Provide DefaultContext
void SbRtl_GetDefaultContext(StarBASIC *, SbxArray & rPar, bool )
{
RTL_Impl_GetDefaultContext( rPar );
}
void SbRtl_Join(StarBASIC *, SbxArray & rPar, bool )
{
sal_uInt32 nParCount = rPar.Count();
if ( nParCount != 3 && nParCount != 2 )
return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
SbxBase* pParObj = rPar.Get(1)->GetObject();
SbxDimArray* pArr = dynamic_cast <SbxDimArray*>( pParObj );
if ( !pArr )
return StarBASIC::Error( ERRCODE_BASIC_MUST_HAVE_DIMS );
if (pArr->GetDims() != 1)
return StarBASIC::Error( ERRCODE_BASIC_WRONG_DIMS ); // Syntax Error?!
OUString aDelim;
if ( nParCount == 3 )
{
aDelim = rPar.Get(2)->GetOUString();
}
else
{
aDelim = " " ;
}
OUStringBuffer aRetStr(32);
sal_Int32 nLower, nUpper;
pArr->GetDim(1, nLower, nUpper);
sal_Int32 aIdx[1];
for (aIdx[0] = nLower; aIdx[0] <= nUpper; ++aIdx[0])
{
OUString aStr = pArr->Get(aIdx)->GetOUString();
aRetStr.append(aStr);
if (aIdx[0] != nUpper)
{
aRetStr.append(aDelim);
}
}
rPar.Get(0)->PutString(aRetStr.makeStringAndClear());
}
void SbRtl_Split(StarBASIC *, SbxArray & rPar, bool )
{
sal_uInt32 nParCount = rPar.Count();
if ( nParCount < 2 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
OUString aExpression = rPar.Get(1)->GetOUString();
sal_Int32 nArraySize = 0;
std::vector< OUString > vRet;
if ( !aExpression.isEmpty() )
{
OUString aDelim;
if ( nParCount >= 3 )
{
aDelim = rPar.Get(2)->GetOUString();
}
else
{
aDelim = " " ;
}
sal_Int32 nCount = -1;
if ( nParCount == 4 )
{
nCount = rPar.Get(3)->GetLong();
}
sal_Int32 nDelimLen = aDelim.getLength();
if ( nDelimLen )
{
sal_Int32 iSearch = -1;
sal_Int32 iStart = 0;
do
{
bool bBreak = false ;
if ( nCount >= 0 && nArraySize == nCount - 1 )
{
bBreak = true ;
}
iSearch = aExpression.indexOf( aDelim, iStart );
OUString aSubStr;
if ( iSearch >= 0 && !bBreak )
{
aSubStr = aExpression.copy( iStart, iSearch - iStart );
iStart = iSearch + nDelimLen;
}
else
{
aSubStr = aExpression.copy( iStart );
}
vRet.push_back( aSubStr );
nArraySize++;
if ( bBreak )
{
break ;
}
}
while ( iSearch >= 0 );
}
else
{
vRet.push_back( aExpression );
nArraySize = 1;
}
}
// tdf#123025 - split returns an array of substrings
SbxDimArray* pArray = new SbxDimArray( SbxSTRING );
pArray->unoAddDim(0, nArraySize - 1);
// insert parameter(s) into the array
const bool bIsVBAInterOp = SbiRuntime::isVBAEnabled();
for (sal_Int32 i = 0 ; i < nArraySize ; i++ )
{
// tdf#123025 - split returns an array of substrings
SbxVariableRef xVar = new SbxVariable( SbxSTRING );
xVar->PutString( vRet[i] );
// tdf#144924 - allow the assignment of different data types to the individual elements
if (!bIsVBAInterOp)
{
xVar->ResetFlag(SbxFlagBits::Fixed);
}
pArray->Put(xVar.get(), &i);
}
// return array
SbxVariableRef refVar = rPar.Get(0);
SbxFlagBits nFlags = refVar->GetFlags();
refVar->ResetFlag( SbxFlagBits::Fixed );
refVar->PutObject( pArray );
refVar->SetFlags( nFlags );
refVar->SetParameters( nullptr );
}
// MonthName(month[, abbreviate])
void SbRtl_MonthName(StarBASIC *, SbxArray & rPar, bool )
{
sal_uInt32 nParCount = rPar.Count();
if ( nParCount != 2 && nParCount != 3 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
const Reference< XCalendar4 >& xCalendar = getLocaleCalendar();
if ( !xCalendar.is() )
{
StarBASIC::Error( ERRCODE_BASIC_INTERNAL_ERROR );
return ;
}
Sequence< CalendarItem2 > aMonthSeq = xCalendar->getMonths2();
sal_Int32 nMonthCount = aMonthSeq.getLength();
sal_Int16 nVal = rPar.Get(1)->GetInteger();
if ( nVal < 1 || nVal > nMonthCount )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
bool bAbbreviate = false ;
if ( nParCount == 3 )
bAbbreviate = rPar.Get(2)->GetBool();
const CalendarItem2* pCalendarItems = aMonthSeq.getConstArray();
const CalendarItem2& rItem = pCalendarItems[nVal - 1];
OUString aRetStr = ( bAbbreviate ? rItem.AbbrevName : rItem.FullName );
rPar.Get(0)->PutString(aRetStr);
}
// WeekdayName(weekday, abbreviate, firstdayofweek)
void SbRtl_WeekdayName(StarBASIC *, SbxArray & rPar, bool )
{
sal_uInt32 nParCount = rPar.Count();
if ( nParCount < 2 || nParCount > 4 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
const Reference< XCalendar4 >& xCalendar = getLocaleCalendar();
if ( !xCalendar.is() )
{
StarBASIC::Error( ERRCODE_BASIC_INTERNAL_ERROR );
return ;
}
Sequence< CalendarItem2 > aDaySeq = xCalendar->getDays2();
sal_Int16 nDayCount = static_cast <sal_Int16>(aDaySeq.getLength());
sal_Int16 nDay = rPar.Get(1)->GetInteger();
sal_Int16 nFirstDay = 0;
if ( nParCount == 4 )
{
nFirstDay = rPar.Get(3)->GetInteger();
if ( nFirstDay < 0 || nFirstDay > 7 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
}
if ( nFirstDay == 0 )
{
nFirstDay = sal_Int16( xCalendar->getFirstDayOfWeek() + 1 );
}
nDay = 1 + (nDay + nDayCount + nFirstDay - 2) % nDayCount;
if ( nDay < 1 || nDay > nDayCount )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
bool bAbbreviate = false ;
if ( nParCount >= 3 )
{
SbxVariable* pPar2 = rPar.Get(2);
if ( !pPar2->IsErr() )
{
bAbbreviate = pPar2->GetBool();
}
}
const CalendarItem2* pCalendarItems = aDaySeq.getConstArray();
const CalendarItem2& rItem = pCalendarItems[nDay - 1];
OUString aRetStr = ( bAbbreviate ? rItem.AbbrevName : rItem.FullName );
rPar.Get(0)->PutString(aRetStr);
}
void SbRtl_Weekday(StarBASIC *, SbxArray & rPar, bool )
{
sal_uInt32 nParCount = rPar.Count();
if ( nParCount < 2 || nParCount > 3 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
}
else
{
double aDate = rPar.Get(1)->GetDate();
bool bFirstDay = false ;
sal_Int16 nFirstDay = 0;
if ( nParCount > 2 )
{
nFirstDay = rPar.Get(2)->GetInteger();
bFirstDay = true ;
}
sal_Int16 nDay = implGetWeekDay( aDate, bFirstDay, nFirstDay );
rPar.Get(0)->PutInteger(nDay);
}
}
namespace {
enum Interval
{
INTERVAL_YYYY,
INTERVAL_Q,
INTERVAL_M,
INTERVAL_Y,
INTERVAL_D,
INTERVAL_W,
INTERVAL_WW,
INTERVAL_H,
INTERVAL_N,
INTERVAL_S
};
struct IntervalInfo
{
Interval meInterval;
char const * mStringCode;
double mdValue;
bool mbSimple;
};
}
static IntervalInfo const * getIntervalInfo( const OUString& rStringCode )
{
static IntervalInfo const aIntervalTable[] =
{
{ INTERVAL_YYYY, "yyyy" , 0.0, false }, // Year
{ INTERVAL_Q, "q" , 0.0, false }, // Quarter
{ INTERVAL_M, "m" , 0.0, false }, // Month
{ INTERVAL_Y, "y" , 1.0, true }, // Day of year
{ INTERVAL_D, "d" , 1.0, true }, // Day
{ INTERVAL_W, "w" , 1.0, true }, // Weekday
{ INTERVAL_WW, "ww" , 7.0, true }, // Week
{ INTERVAL_H, "h" , 1.0 / 24.0, true }, // Hour
{ INTERVAL_N, "n" , 1.0 / 1440.0, true }, // Minute
{ INTERVAL_S, "s" , 1.0 / 86400.0, true } // Second
};
auto const pred = [&rStringCode](const IntervalInfo &aInterval) {
return rStringCode.equalsIgnoreAsciiCaseAscii(aInterval.mStringCode);
};
auto intervalIter = std::find_if(std::begin(aIntervalTable), std::end(aIntervalTable), pred);
if (intervalIter != std::end(aIntervalTable)) {
return intervalIter;
}
return nullptr;
}
static void implGetDayMonthYear( sal_Int16& rnYear, sal_Int16& rnMonth, sal_Int16& rnDay, double dDate )
{
rnDay = implGetDateDay( dDate );
rnMonth = implGetDateMonth( dDate );
rnYear = implGetDateYear( dDate );
}
/** Limits a date to valid dates within tools' class Date capabilities.
@return the year number, truncated if necessary and in that case also
rMonth and rDay adjusted.
*/
static sal_Int16 limitDate( sal_Int32 n32Year, sal_Int16& rMonth, sal_Int16& rDay )
{
if ( n32Year > SAL_MAX_INT16 )
{
n32Year = SAL_MAX_INT16;
rMonth = 12;
rDay = 31;
}
else if ( n32Year < SAL_MIN_INT16 )
{
n32Year = SAL_MIN_INT16;
rMonth = 1;
rDay = 1;
}
return static_cast <sal_Int16>(n32Year);
}
void SbRtl_DateAdd(StarBASIC *, SbxArray & rPar, bool )
{
sal_uInt32 nParCount = rPar.Count();
if ( nParCount != 4 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
OUString aStringCode = rPar.Get(1)->GetOUString();
IntervalInfo const * pInfo = getIntervalInfo( aStringCode );
if ( !pInfo )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
sal_Int32 lNumber = rPar.Get(2)->GetLong();
double dDate = rPar.Get(3)->GetDate();
double dNewDate = 0;
if ( pInfo->mbSimple )
{
double dAdd = pInfo->mdValue * lNumber;
dNewDate = dDate + dAdd;
}
else
{
// Keep hours, minutes, seconds
double dHoursMinutesSeconds = dDate - floor( dDate );
bool bOk = true ;
sal_Int16 nYear, nMonth, nDay;
sal_Int16 nTargetYear16 = 0, nTargetMonth = 0;
implGetDayMonthYear( nYear, nMonth, nDay, dDate );
switch ( pInfo->meInterval )
{
case INTERVAL_YYYY:
{
sal_Int32 nTargetYear = lNumber + nYear;
nTargetYear16 = limitDate( nTargetYear, nMonth, nDay );
/* TODO: should the result be error if the date was limited? It never was. */
nTargetMonth = nMonth;
bOk = implDateSerial( nTargetYear16, nTargetMonth, nDay, false , SbDateCorrection::TruncateToMonth, dNewDate );
break ;
}
case INTERVAL_Q:
case INTERVAL_M:
{
bool bNeg = (lNumber < 0);
if ( bNeg )
lNumber = -lNumber;
sal_Int32 nYearsAdd;
sal_Int16 nMonthAdd;
if ( pInfo->meInterval == INTERVAL_Q )
{
nYearsAdd = lNumber / 4;
nMonthAdd = static_cast <sal_Int16>( 3 * (lNumber % 4) );
}
else
{
nYearsAdd = lNumber / 12;
nMonthAdd = static_cast <sal_Int16>( lNumber % 12 );
}
sal_Int32 nTargetYear;
if ( bNeg )
{
nTargetMonth = nMonth - nMonthAdd;
if ( nTargetMonth <= 0 )
{
nTargetMonth += 12;
nYearsAdd++;
}
nTargetYear = static_cast <sal_Int32>(nYear) - nYearsAdd;
}
else
{
nTargetMonth = nMonth + nMonthAdd;
if ( nTargetMonth > 12 )
{
nTargetMonth -= 12;
nYearsAdd++;
}
nTargetYear = static_cast <sal_Int32>(nYear) + nYearsAdd;
}
nTargetYear16 = limitDate( nTargetYear, nTargetMonth, nDay );
/* TODO: should the result be error if the date was limited? It never was. */
bOk = implDateSerial( nTargetYear16, nTargetMonth, nDay, false , SbDateCorrection::TruncateToMonth, dNewDate );
break ;
}
default : break ;
}
if ( bOk )
dNewDate += dHoursMinutesSeconds;
}
rPar.Get(0)->PutDate(dNewDate);
}
static double RoundImpl( double d )
{
return ( d >= 0 ) ? floor( d + 0.5 ) : -floor( -d + 0.5 );
}
void SbRtl_DateDiff(StarBASIC *, SbxArray & rPar, bool )
{
// DateDiff(interval, date1, date2[, firstdayofweek[, firstweekofyear]])
sal_uInt32 nParCount = rPar.Count();
if ( nParCount < 4 || nParCount > 6 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
OUString aStringCode = rPar.Get(1)->GetOUString();
IntervalInfo const * pInfo = getIntervalInfo( aStringCode );
if ( !pInfo )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
double dDate1 = rPar.Get(2)->GetDate();
double dDate2 = rPar.Get(3)->GetDate();
double dRet = 0.0;
switch ( pInfo->meInterval )
{
case INTERVAL_YYYY:
{
sal_Int16 nYear1 = implGetDateYear( dDate1 );
sal_Int16 nYear2 = implGetDateYear( dDate2 );
dRet = nYear2 - nYear1;
break ;
}
case INTERVAL_Q:
{
sal_Int16 nYear1 = implGetDateYear( dDate1 );
sal_Int16 nYear2 = implGetDateYear( dDate2 );
sal_Int16 nQ1 = 1 + (implGetDateMonth( dDate1 ) - 1) / 3;
sal_Int16 nQ2 = 1 + (implGetDateMonth( dDate2 ) - 1) / 3;
sal_Int16 nQGes1 = 4 * nYear1 + nQ1;
sal_Int16 nQGes2 = 4 * nYear2 + nQ2;
dRet = nQGes2 - nQGes1;
break ;
}
case INTERVAL_M:
{
sal_Int16 nYear1 = implGetDateYear( dDate1 );
sal_Int16 nYear2 = implGetDateYear( dDate2 );
sal_Int16 nMonth1 = implGetDateMonth( dDate1 );
sal_Int16 nMonth2 = implGetDateMonth( dDate2 );
sal_Int16 nMonthGes1 = 12 * nYear1 + nMonth1;
sal_Int16 nMonthGes2 = 12 * nYear2 + nMonth2;
dRet = nMonthGes2 - nMonthGes1;
break ;
}
case INTERVAL_Y:
case INTERVAL_D:
{
double dDays1 = floor( dDate1 );
double dDays2 = floor( dDate2 );
dRet = dDays2 - dDays1;
break ;
}
case INTERVAL_W:
case INTERVAL_WW:
{
double dDays1 = floor( dDate1 );
double dDays2 = floor( dDate2 );
if ( pInfo->meInterval == INTERVAL_WW )
{
sal_Int16 nFirstDay = 1; // Default
if ( nParCount >= 5 )
{
nFirstDay = rPar.Get(4)->GetInteger();
if ( nFirstDay < 0 || nFirstDay > 7 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
if ( nFirstDay == 0 )
{
const Reference< XCalendar4 >& xCalendar = getLocaleCalendar();
if ( !xCalendar.is() )
{
StarBASIC::Error( ERRCODE_BASIC_INTERNAL_ERROR );
return ;
}
nFirstDay = sal_Int16( xCalendar->getFirstDayOfWeek() + 1 );
}
}
sal_Int16 nDay1 = implGetWeekDay( dDate1 );
sal_Int16 nDay1_Diff = nDay1 - nFirstDay;
if ( nDay1_Diff < 0 )
nDay1_Diff += 7;
dDays1 -= nDay1_Diff;
sal_Int16 nDay2 = implGetWeekDay( dDate2 );
sal_Int16 nDay2_Diff = nDay2 - nFirstDay;
if ( nDay2_Diff < 0 )
nDay2_Diff += 7;
dDays2 -= nDay2_Diff;
}
double dDiff = dDays2 - dDays1;
dRet = ( dDiff >= 0 ) ? floor( dDiff / 7.0 ) : -floor( -dDiff / 7.0 );
break ;
}
case INTERVAL_H:
{
dRet = RoundImpl( 24.0 * (dDate2 - dDate1) );
break ;
}
case INTERVAL_N:
{
dRet = RoundImpl( 1440.0 * (dDate2 - dDate1) );
break ;
}
case INTERVAL_S:
{
dRet = RoundImpl( 86400.0 * (dDate2 - dDate1) );
break ;
}
}
rPar.Get(0)->PutDouble(dRet);
}
static double implGetDateOfFirstDayInFirstWeek
( sal_Int16 nYear, sal_Int16& nFirstDay, sal_Int16& nFirstWeek, bool * pbError = nullptr )
{
ErrCode nError = ERRCODE_NONE;
if ( nFirstDay < 0 || nFirstDay > 7 )
nError = ERRCODE_BASIC_BAD_ARGUMENT;
if ( nFirstWeek < 0 || nFirstWeek > 3 )
nError = ERRCODE_BASIC_BAD_ARGUMENT;
Reference< XCalendar4 > xCalendar;
if ( nFirstDay == 0 || nFirstWeek == 0 )
{
xCalendar = getLocaleCalendar();
if ( !xCalendar.is() )
nError = ERRCODE_BASIC_BAD_ARGUMENT;
}
if ( nError != ERRCODE_NONE )
{
StarBASIC::Error( nError );
if ( pbError )
*pbError = true ;
return 0.0;
}
if ( nFirstDay == 0 )
nFirstDay = sal_Int16( xCalendar->getFirstDayOfWeek() + 1 );
sal_Int16 nFirstWeekMinDays = 0; // Not used for vbFirstJan1 = default
if ( nFirstWeek == 0 )
{
nFirstWeekMinDays = xCalendar->getMinimumNumberOfDaysForFirstWeek();
if ( nFirstWeekMinDays == 1 )
{
nFirstWeekMinDays = 0;
nFirstWeek = 1;
}
else if ( nFirstWeekMinDays == 4 )
nFirstWeek = 2;
else if ( nFirstWeekMinDays == 7 )
nFirstWeek = 3;
}
else if ( nFirstWeek == 2 )
nFirstWeekMinDays = 4; // vbFirstFourDays
else if ( nFirstWeek == 3 )
nFirstWeekMinDays = 7; // vbFirstFourDays
double dBaseDate;
implDateSerial( nYear, 1, 1, false , SbDateCorrection::None, dBaseDate );
sal_Int16 nWeekDay0101 = implGetWeekDay( dBaseDate );
sal_Int16 nDayDiff = nWeekDay0101 - nFirstDay;
if ( nDayDiff < 0 )
nDayDiff += 7;
if ( nFirstWeekMinDays )
{
sal_Int16 nThisWeeksDaysInYearCount = 7 - nDayDiff;
if ( nThisWeeksDaysInYearCount < nFirstWeekMinDays )
nDayDiff -= 7;
}
double dRetDate = dBaseDate - nDayDiff;
return dRetDate;
}
void SbRtl_DatePart(StarBASIC *, SbxArray & rPar, bool )
{
// DatePart(interval, date[,firstdayofweek[, firstweekofyear]])
sal_uInt32 nParCount = rPar.Count();
if ( nParCount < 3 || nParCount > 5 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
OUString aStringCode = rPar.Get(1)->GetOUString();
IntervalInfo const * pInfo = getIntervalInfo( aStringCode );
if ( !pInfo )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
double dDate = rPar.Get(2)->GetDate();
sal_Int32 nRet = 0;
switch ( pInfo->meInterval )
{
case INTERVAL_YYYY:
{
nRet = implGetDateYear( dDate );
break ;
}
case INTERVAL_Q:
{
nRet = 1 + (implGetDateMonth( dDate ) - 1) / 3;
break ;
}
case INTERVAL_M:
{
nRet = implGetDateMonth( dDate );
break ;
}
case INTERVAL_Y:
{
sal_Int16 nYear = implGetDateYear( dDate );
double dBaseDate;
implDateSerial( nYear, 1, 1, false , SbDateCorrection::None, dBaseDate );
nRet = 1 + sal_Int32( dDate - dBaseDate );
break ;
}
case INTERVAL_D:
{
nRet = implGetDateDay( dDate );
break ;
}
case INTERVAL_W:
{
bool bFirstDay = false ;
sal_Int16 nFirstDay = 1; // Default
if ( nParCount >= 4 )
{
nFirstDay = rPar.Get(3)->GetInteger();
bFirstDay = true ;
}
nRet = implGetWeekDay( dDate, bFirstDay, nFirstDay );
break ;
}
case INTERVAL_WW:
{
sal_Int16 nFirstDay = 1; // Default
if ( nParCount >= 4 )
nFirstDay = rPar.Get(3)->GetInteger();
sal_Int16 nFirstWeek = 1; // Default
if ( nParCount == 5 )
nFirstWeek = rPar.Get(4)->GetInteger();
sal_Int16 nYear = implGetDateYear( dDate );
bool bError = false ;
double dYearFirstDay = implGetDateOfFirstDayInFirstWeek( nYear, nFirstDay, nFirstWeek, &bError );
if ( !bError )
{
if ( dYearFirstDay > dDate )
{
// Date belongs to last year's week
dYearFirstDay = implGetDateOfFirstDayInFirstWeek( nYear - 1, nFirstDay, nFirstWeek );
}
else if ( nFirstWeek != 1 )
{
// Check if date belongs to next year
double dNextYearFirstDay = implGetDateOfFirstDayInFirstWeek( nYear + 1, nFirstDay, nFirstWeek );
if ( dDate >= dNextYearFirstDay )
dYearFirstDay = dNextYearFirstDay;
}
// Calculate week
double dDiff = dDate - dYearFirstDay;
nRet = 1 + sal_Int32( dDiff / 7 );
}
break ;
}
case INTERVAL_H:
{
nRet = implGetHour( dDate );
break ;
}
case INTERVAL_N:
{
nRet = implGetMinute( dDate );
break ;
}
case INTERVAL_S:
{
nRet = implGetSecond( dDate );
break ;
}
}
rPar.Get(0)->PutLong(nRet);
}
// FormatDateTime(Date[,NamedFormat])
void SbRtl_FormatDateTime(StarBASIC *, SbxArray & rPar, bool )
{
sal_uInt32 nParCount = rPar.Count();
if ( nParCount < 2 || nParCount > 3 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
double dDate = rPar.Get(1)->GetDate();
sal_Int16 nNamedFormat = 0;
if ( nParCount > 2 )
{
nNamedFormat = rPar.Get(2)->GetInteger();
if ( nNamedFormat < 0 || nNamedFormat > 4 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
}
const Reference< XCalendar4 >& xCalendar = getLocaleCalendar();
if ( !xCalendar.is() )
{
StarBASIC::Error( ERRCODE_BASIC_INTERNAL_ERROR );
return ;
}
OUString aRetStr;
SbxVariableRef pSbxVar = new SbxVariable( SbxSTRING );
switch ( nNamedFormat )
{
// GeneralDate:
// Display a date and/or time. If there is a date part,
// display it as a short date. If there is a time part,
// display it as a long time. If present, both parts are displayed.
// 12/21/2004 11:24:50 AM
// 21.12.2004 12:13:51
case 0:
pSbxVar->PutDate( dDate );
aRetStr = pSbxVar->GetOUString();
break ;
// LongDate: Display a date using the long date format specified
// in your computer's regional settings.
// Tuesday, December 21, 2004
// Dienstag, 21. December 2004
case 1:
{
std::shared_ptr<SvNumberFormatter> pFormatter;
if ( GetSbData()->pInst )
{
pFormatter = GetSbData()->pInst->GetNumberFormatter();
}
else
{
sal_uInt32 n; // Dummy
pFormatter = SbiInstance::PrepareNumberFormatter( n, n, n );
}
LanguageType eLangType = Application::GetSettings().GetLanguageTag().getLanguageType();
const sal_uInt32 nIndex = pFormatter->GetFormatIndex( NF_DATE_SYSTEM_LONG, eLangType );
const Color* pCol;
pFormatter->GetOutputString( dDate, nIndex, aRetStr, &pCol );
break ;
}
// ShortDate: Display a date using the short date format specified
// in your computer's regional settings.
// 21.12.2004
case 2:
pSbxVar->PutDate( floor(dDate) );
aRetStr = pSbxVar->GetOUString();
break ;
// LongTime: Display a time using the time format specified
// in your computer's regional settings.
// 11:24:50 AM
// 12:13:51
case 3:
// ShortTime: Display a time using the 24-hour format (hh:mm).
// 11:24
case 4:
double dTime = modf( dDate, &o3tl::temporary(double ()) );
pSbxVar->PutDate( dTime );
if ( nNamedFormat == 3 )
{
aRetStr = pSbxVar->GetOUString();
}
else
{
aRetStr = pSbxVar->GetOUString().copy( 0, 5 );
}
break ;
}
rPar.Get(0)->PutString(aRetStr);
}
void SbRtl_Frac(StarBASIC *, SbxArray & rPar, bool )
{
sal_uInt32 nParCount = rPar.Count();
if ( nParCount != 2)
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
SbxVariable* pSbxVariable = rPar.Get(1);
double dVal = pSbxVariable->GetDouble();
if (dVal >= 0)
rPar.Get(0)->PutDouble(dVal - ::rtl::math::approxFloor(dVal));
else
rPar.Get(0)->PutDouble(dVal - ::rtl::math::approxCeil(dVal));
}
void SbRtl_Round(StarBASIC *, SbxArray & rPar, bool )
{
sal_uInt32 nParCount = rPar.Count();
if ( nParCount != 2 && nParCount != 3 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
SbxVariable* pSbxVariable = rPar.Get(1);
double dVal = pSbxVariable->GetDouble();
double dRes = 0.0;
if ( dVal != 0.0 )
{
sal_Int16 numdecimalplaces = 0;
if ( nParCount == 3 )
{
numdecimalplaces = rPar.Get(2)->GetInteger();
if ( numdecimalplaces < 0 || numdecimalplaces > 22 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
}
dRes = rtl_math_round(dVal, numdecimalplaces, rtl_math_RoundingMode_HalfEven);
}
rPar.Get(0)->PutDouble(dRes);
}
static void CallFunctionAccessFunction( const Sequence< Any >& aArgs, const OUString& sFuncName, SbxVariable* pRet )
{
static Reference< XFunctionAccess > xFunc;
try
{
if ( !xFunc.is() )
{
Reference< XMultiServiceFactory > xFactory( getProcessServiceFactory() );
if ( xFactory.is() )
{
xFunc.set( xFactory->createInstance(u"com.sun.star.sheet.FunctionAccess" _ustr), UNO_QUERY_THROW);
}
}
Any aRet = xFunc->callFunction( sFuncName, aArgs );
unoToSbxValue( pRet, aRet );
}
catch (const Exception& )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
}
}
void SbRtl_SYD(StarBASIC *, SbxArray & rPar, bool )
{
sal_uInt32 nArgCount = rPar.Count() - 1;
if ( nArgCount < 4 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
// retrieve non-optional params
Sequence< Any > aParams
{
Any(rPar.Get(1)->GetDouble()),
Any(rPar.Get(2)->GetDouble()),
Any(rPar.Get(3)->GetDouble()),
Any(rPar.Get(4)->GetDouble())
};
CallFunctionAccessFunction(aParams, u"SYD" _ustr, rPar.Get(0));
}
void SbRtl_SLN(StarBASIC *, SbxArray & rPar, bool )
{
sal_uInt32 nArgCount = rPar.Count() - 1;
if ( nArgCount < 3 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
// retrieve non-optional params
Sequence< Any > aParams
{
Any(rPar.Get(1)->GetDouble()),
Any(rPar.Get(2)->GetDouble()),
Any(rPar.Get(3)->GetDouble())
};
CallFunctionAccessFunction(aParams, u"SLN" _ustr, rPar.Get(0));
}
void SbRtl_Pmt(StarBASIC *, SbxArray & rPar, bool )
{
sal_uInt32 nArgCount = rPar.Count() - 1;
if ( nArgCount < 3 || nArgCount > 5 )
{
StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
return ;
}
// retrieve non-optional params
double rate = rPar.Get(1)->GetDouble();
double nper = rPar.Get(2)->GetDouble();
double pmt = rPar.Get(3)->GetDouble();
// set default values for Optional args
double fv = 0;
double type = 0;
// fv
if ( nArgCount >= 4 )
{
if (rPar.Get(4)->GetType() != SbxEMPTY)
fv = rPar.Get(4)->GetDouble();
}
--> --------------------
--> maximum size reached
--> --------------------
quality 96%
¤ Dauer der Verarbeitung: 0.36 Sekunden
(vorverarbeitet)
¤
*© Formatika GbR, Deutschland