diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sc/source/core/tool | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sc/source/core/tool')
95 files changed, 83856 insertions, 0 deletions
diff --git a/sc/source/core/tool/addincfg.cxx b/sc/source/core/tool/addincfg.cxx new file mode 100644 index 0000000000..3f09b7defd --- /dev/null +++ b/sc/source/core/tool/addincfg.cxx @@ -0,0 +1,53 @@ +/* -*- 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 <com/sun/star/uno/Sequence.hxx> + +#include <sal/log.hxx> + +#include <global.hxx> +#include <addincol.hxx> +#include <addincfg.hxx> +#include <scmod.hxx> +#include <sc.hrc> + +using namespace com::sun::star; + +constexpr OUStringLiteral CFGPATH_ADDINS = u"Office.CalcAddIns/AddInInfo"; + +ScAddInCfg::ScAddInCfg() + : ConfigItem(CFGPATH_ADDINS) +{ + EnableNotification({ {} }); +} + +void ScAddInCfg::ImplCommit() { SAL_WARN("sc", "ScAddInCfg shouldn't be modified"); } + +void ScAddInCfg::Notify(const uno::Sequence<OUString>&) +{ + // forget all add-in information, re-initialize when needed next time + ScGlobal::GetAddInCollection()->Clear(); + + // function list must also be rebuilt, but can't be modified while function + // autopilot is open (function list for autopilot is then still old) + if (SC_MOD()->GetCurRefDlgId() != SID_OPENDLG_FUNCTION) + ScGlobal::ResetFunctionList(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/addincol.cxx b/sc/source/core/tool/addincol.cxx new file mode 100644 index 0000000000..a18800781b --- /dev/null +++ b/sc/source/core/tool/addincol.cxx @@ -0,0 +1,1731 @@ +/* -*- 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 <comphelper/processfactory.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <sfx2/objsh.hxx> +#include <unotools/charclass.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> + +#include <com/sun/star/container/XContentEnumerationAccess.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XServiceName.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/lang/XSingleComponentFactory.hpp> +#include <com/sun/star/reflection/XIdlClass.hpp> +#include <com/sun/star/beans/XIntrospectionAccess.hpp> +#include <com/sun/star/beans/theIntrospection.hpp> +#include <com/sun/star/beans/MethodConcept.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/table/XCellRange.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/sheet/XCompatibilityNames.hpp> +#include <com/sun/star/sheet/NoConvergenceException.hpp> +#include <com/sun/star/sheet/XAddIn.hpp> +#include <com/sun/star/sheet/XVolatileResult.hpp> + +#include <addincol.hxx> +#include <addinhelpid.hxx> +#include <scmatrix.hxx> +#include <formula/errorcodes.hxx> +#include <formula/funcvarargs.h> +#include <optutil.hxx> +#include <addincfg.hxx> +#include <scmod.hxx> +#include <rangeseq.hxx> +#include <funcdesc.hxx> +#include <svl/sharedstring.hxx> +#include <formulaopt.hxx> +#include <compiler.hxx> +#include <document.hxx> +#include <memory> + +using namespace com::sun::star; + +#define SC_CALLERPOS_NONE (-1) + +ScUnoAddInFuncData::ScUnoAddInFuncData( const OUString& rNam, const OUString& rLoc, + OUString aDesc, + sal_uInt16 nCat, OUString sHelp, + uno::Reference<reflection::XIdlMethod> xFunc, + uno::Any aO, + tools::Long nAC, const ScAddInArgDesc* pAD, + tools::Long nCP ) : + aOriginalName( rNam ), + aLocalName( rLoc ), + aUpperName( rNam ), + aUpperLocal( rLoc ), + aDescription(std::move( aDesc )), + xFunction(std::move( xFunc )), + aObject(std::move( aO )), + nArgCount( nAC ), + nCallerPos( nCP ), + nCategory( nCat ), + sHelpId(std::move( sHelp )), + bCompInitialized( false ) +{ + if ( nArgCount ) + { + pArgDescs.reset( new ScAddInArgDesc[nArgCount] ); + for (tools::Long i=0; i<nArgCount; i++) + pArgDescs[i] = pAD[i]; + } + + aUpperName = aUpperName.toAsciiUpperCase(); // programmatic name + aUpperLocal = ScGlobal::getCharClass().uppercase(aUpperLocal); +} + +ScUnoAddInFuncData::~ScUnoAddInFuncData() +{ +} + +const ::std::vector<ScUnoAddInFuncData::LocalizedName>& ScUnoAddInFuncData::GetCompNames() const +{ + if ( !bCompInitialized ) + { + // read sequence of compatibility names on demand + + uno::Reference<sheet::XAddIn> xAddIn; + if ( aObject >>= xAddIn ) + { + uno::Reference<sheet::XCompatibilityNames> xComp( xAddIn, uno::UNO_QUERY ); + if ( xComp.is() && xFunction.is() ) + { + OUString aMethodName = xFunction->getName(); + const uno::Sequence< sheet::LocalizedName> aCompNames( xComp->getCompatibilityNames( aMethodName )); + maCompNames.clear(); + for (const sheet::LocalizedName& rCompName : aCompNames) + { + maCompNames.emplace_back( + LanguageTag::convertToBcp47( rCompName.Locale, false), + rCompName.Name); + } + } + } + + bCompInitialized = true; // also if not successful + } + return maCompNames; +} + +void ScUnoAddInFuncData::SetCompNames( ::std::vector< ScUnoAddInFuncData::LocalizedName >&& rNew ) +{ + OSL_ENSURE( !bCompInitialized, "SetCompNames after initializing" ); + + maCompNames = std::move(rNew); + + bCompInitialized = true; +} + +void ScUnoAddInFuncData::SetEnglishName( const OUString& rEnglishName ) +{ + if (!rEnglishName.isEmpty()) + aUpperEnglish = ScCompiler::GetCharClassEnglish()->uppercase(rEnglishName); + else + { + // A dumb fallback to not have an empty name, mainly just for the + // assignment to ScFuncDesc::mxFuncName for the Function Wizard and + // formula input tooltips. + aUpperEnglish = aUpperLocal; + } +} + +bool ScUnoAddInFuncData::GetExcelName( const LanguageTag& rDestLang, OUString& rRetExcelName, bool bFallbackToAny ) const +{ + const ::std::vector<LocalizedName>& rCompNames = GetCompNames(); + if ( !rCompNames.empty() ) + { + const OUString& aSearch( rDestLang.getBcp47()); + + // First, check exact match without fallback overhead. + ::std::vector<LocalizedName>::const_iterator itNames = std::find_if(rCompNames.begin(), rCompNames.end(), + [&aSearch](const LocalizedName& rName) { return rName.maLocale == aSearch; }); + if (itNames != rCompNames.end()) + { + rRetExcelName = (*itNames).maName; + return true; + } + + // Second, try match of fallback search with fallback locales, + // appending also 'en-US' and 'en' to search if not queried. + ::std::vector< OUString > aFallbackSearch( rDestLang.getFallbackStrings( true)); + if (aSearch != "en-US") + { + aFallbackSearch.emplace_back("en-US"); + if (aSearch != "en") + { + aFallbackSearch.emplace_back("en"); + } + } + for (const auto& rSearch : aFallbackSearch) + { + for (const auto& rCompName : rCompNames) + { + // We checked already the full tag, start with second. + ::std::vector< OUString > aFallbackLocales( LanguageTag( rCompName.maLocale).getFallbackStrings( false)); + if (std::find(aFallbackLocales.begin(), aFallbackLocales.end(), rSearch) != aFallbackLocales.end()) + { + rRetExcelName = rCompName.maName; + return true; + } + } + } + + if (bFallbackToAny) + { + // Third, last resort, use first (default) entry. + rRetExcelName = rCompNames[0].maName; + return true; + } + } + return false; +} + +void ScUnoAddInFuncData::SetFunction( const uno::Reference< reflection::XIdlMethod>& rNewFunc, const uno::Any& rNewObj ) +{ + xFunction = rNewFunc; + aObject = rNewObj; +} + +void ScUnoAddInFuncData::SetArguments( tools::Long nNewCount, const ScAddInArgDesc* pNewDescs ) +{ + nArgCount = nNewCount; + if ( nArgCount ) + { + pArgDescs.reset( new ScAddInArgDesc[nArgCount] ); + for (tools::Long i=0; i<nArgCount; i++) + pArgDescs[i] = pNewDescs[i]; + } + else + pArgDescs.reset(); +} + +void ScUnoAddInFuncData::SetCallerPos( tools::Long nNewPos ) +{ + nCallerPos = nNewPos; +} + +ScUnoAddInCollection::ScUnoAddInCollection() : + nFuncCount( 0 ), + bInitialized( false ) +{ +} + +ScUnoAddInCollection::~ScUnoAddInCollection() +{ +} + +void ScUnoAddInCollection::Clear() +{ + pExactHashMap.reset(); + pNameHashMap.reset(); + pLocalHashMap.reset(); + pEnglishHashMap.reset(); + ppFuncData.reset(); + nFuncCount = 0; + + bInitialized = false; +} + +void ScUnoAddInCollection::Initialize() +{ + OSL_ENSURE( !bInitialized, "Initialize twice?" ); + + uno::Reference<lang::XMultiServiceFactory> xManager = comphelper::getProcessServiceFactory(); + uno::Reference<container::XContentEnumerationAccess> xEnAc( xManager, uno::UNO_QUERY ); + if ( xEnAc.is() ) + { + uno::Reference<container::XEnumeration> xEnum = + xEnAc->createContentEnumeration( "com.sun.star.sheet.AddIn" ); + if ( xEnum.is() ) + { + // loop through all AddIns + while ( xEnum->hasMoreElements() ) + { + uno::Any aAddInAny = xEnum->nextElement(); + + try + { + uno::Reference<uno::XInterface> xIntFac; + aAddInAny >>= xIntFac; + if ( xIntFac.is() ) + { + // #i59984# try XSingleComponentFactory in addition to (old) XSingleServiceFactory, + // passing the context to the component + + uno::Reference<uno::XInterface> xInterface; + uno::Reference<uno::XComponentContext> xCtx( + comphelper::getComponentContext(xManager)); + uno::Reference<lang::XSingleComponentFactory> xCFac( xIntFac, uno::UNO_QUERY ); + if (xCFac.is()) + { + xInterface = xCFac->createInstanceWithContext(xCtx); + if (xInterface.is()) + ReadFromAddIn( xInterface ); + } + + if (!xInterface.is()) + { + uno::Reference<lang::XSingleServiceFactory> xFac( xIntFac, uno::UNO_QUERY ); + if ( xFac.is() ) + { + xInterface = xFac->createInstance(); + if (xInterface.is()) + ReadFromAddIn( xInterface ); + } + } + } + } catch ( const uno::Exception& ) { + SAL_WARN ( "sc", "Failed to initialize create instance of sheet.AddIn" ); + } + } + } + } + + // ReadConfiguration is called after looking at the AddIn implementations. + // Duplicated are skipped (by using the service information, they don't have to be updated again + // when argument information is needed). + ReadConfiguration(); + + bInitialized = true; // with or without functions +} + +static sal_uInt16 lcl_GetCategory( std::u16string_view rName ) +{ + static const char* aFuncNames[SC_FUNCGROUP_COUNT] = + { + // array index = ID - 1 (ID starts at 1) + // all upper case + "Database", // ID_FUNCTION_GRP_DATABASE + "Date&Time", // ID_FUNCTION_GRP_DATETIME + "Financial", // ID_FUNCTION_GRP_FINANCIAL + "Information", // ID_FUNCTION_GRP_INFO + "Logical", // ID_FUNCTION_GRP_LOGIC + "Mathematical", // ID_FUNCTION_GRP_MATH + "Matrix", // ID_FUNCTION_GRP_MATRIX + "Statistical", // ID_FUNCTION_GRP_STATISTIC + "Spreadsheet", // ID_FUNCTION_GRP_TABLE + "Text", // ID_FUNCTION_GRP_TEXT + "Add-In" // ID_FUNCTION_GRP_ADDINS + }; + for (sal_uInt16 i=0; i<SC_FUNCGROUP_COUNT; i++) + if ( o3tl::equalsAscii( rName, aFuncNames[i] ) ) + return i+1; // IDs start at 1 + + return ID_FUNCTION_GRP_ADDINS; // if not found, use Add-In group +} + +constexpr OUStringLiteral CFGPATH_ADDINS = u"Office.CalcAddIns/AddInInfo"; +constexpr OUStringLiteral CFGSTR_ADDINFUNCTIONS = u"AddInFunctions"; + +#define CFG_FUNCPROP_DISPLAYNAME 0 +#define CFG_FUNCPROP_DESCRIPTION 1 +#define CFG_FUNCPROP_CATEGORY 2 +#define CFG_FUNCPROP_COUNT 3 +constexpr OUString CFGSTR_DISPLAYNAME = u"DisplayName"_ustr; +constexpr OUString CFGSTR_DESCRIPTION = u"Description"_ustr; +constexpr OUString CFGSTR_CATEGORY = u"Category"_ustr; +// CategoryDisplayName is ignored for now + +constexpr OUStringLiteral CFGSTR_COMPATIBILITYNAME = u"CompatibilityName"; +constexpr OUStringLiteral CFGSTR_PARAMETERS = u"Parameters"; + +void ScUnoAddInCollection::ReadConfiguration() +{ + // called only from Initialize + + ScAddInCfg& rAddInConfig = SC_MOD()->GetAddInCfg(); + + // Additional, temporary config item for the display names and + // compatibility names. + ScLinkConfigItem aAllLocalesConfig( CFGPATH_ADDINS, ConfigItemMode::AllLocales ); + // CommitLink is not used (only reading values) + + const OUString sSlash('/'); + + // get the list of add-ins (services) + const uno::Sequence<OUString> aServiceNames = rAddInConfig.GetNodeNames( "" ); + + for ( const OUString& aServiceName : aServiceNames ) + { + ScUnoAddInHelpIdGenerator aHelpIdGenerator( aServiceName ); + + OUString aFunctionsPath(aServiceName + sSlash + CFGSTR_ADDINFUNCTIONS); + + uno::Sequence<OUString> aFunctionNames = rAddInConfig.GetNodeNames( aFunctionsPath ); + sal_Int32 nNewCount = aFunctionNames.getLength(); + + // allocate pointers + + tools::Long nOld = nFuncCount; + nFuncCount = nNewCount+nOld; + if ( nOld ) + { + std::unique_ptr<std::unique_ptr<ScUnoAddInFuncData>[]> ppNew(new std::unique_ptr<ScUnoAddInFuncData>[nFuncCount]); + for (tools::Long i=0; i<nOld; i++) + ppNew[i] = std::move(ppFuncData[i]); + ppFuncData = std::move(ppNew); + } + else + ppFuncData.reset( new std::unique_ptr<ScUnoAddInFuncData>[nFuncCount] ); + + //TODO: adjust bucket count? + if ( !pExactHashMap ) + pExactHashMap.reset( new ScAddInHashMap ); + if ( !pNameHashMap ) + pNameHashMap.reset( new ScAddInHashMap ); + if ( !pLocalHashMap ) + pLocalHashMap.reset( new ScAddInHashMap ); + if ( !pEnglishHashMap ) + pEnglishHashMap.reset( new ScAddInHashMap ); + + //TODO: get the function information in a single call for all functions? + + const OUString* pFuncNameArray = aFunctionNames.getConstArray(); + for ( sal_Int32 nFuncPos = 0; nFuncPos < nNewCount; nFuncPos++ ) + { + ppFuncData[nFuncPos+nOld] = nullptr; + + // stored function name: (service name).(function) + OUString aFuncName = aServiceName + "." + pFuncNameArray[nFuncPos]; + + // skip the function if already known (read from old AddIn service) + + if ( pExactHashMap->find( aFuncName ) == pExactHashMap->end() ) + { + OUString aEnglishName; + OUString aLocalName; + OUString aDescription; + sal_uInt16 nCategory = ID_FUNCTION_GRP_ADDINS; + + // get direct information on the function + + OUString aFuncPropPath = aFunctionsPath + sSlash + pFuncNameArray[nFuncPos] + sSlash; + + uno::Sequence<OUString> aFuncPropNames{ + (aFuncPropPath + CFGSTR_DISPLAYNAME), // CFG_FUNCPROP_DISPLAYNAME + (aFuncPropPath + CFGSTR_DESCRIPTION), // CFG_FUNCPROP_DESCRIPTION + (aFuncPropPath + CFGSTR_CATEGORY)}; // CFG_FUNCPROP_CATEGORY + + uno::Sequence<uno::Any> aFuncProperties = rAddInConfig.GetProperties( aFuncPropNames ); + if ( aFuncProperties.getLength() == CFG_FUNCPROP_COUNT ) + { + aFuncProperties[CFG_FUNCPROP_DISPLAYNAME] >>= aLocalName; + aFuncProperties[CFG_FUNCPROP_DESCRIPTION] >>= aDescription; + + OUString aCategoryName; + aFuncProperties[CFG_FUNCPROP_CATEGORY] >>= aCategoryName; + nCategory = lcl_GetCategory( aCategoryName ); + } + + // get English display name + + OUString aDisplayNamePath(aFuncPropPath + CFGSTR_DISPLAYNAME); + uno::Sequence<OUString> aDisplayNamePropNames( &aDisplayNamePath, 1 ); + + uno::Sequence<uno::Any> aDisplayNameProperties = aAllLocalesConfig.GetProperties( aDisplayNamePropNames ); + if ( aDisplayNameProperties.getLength() == 1 ) + { + uno::Sequence<beans::PropertyValue> aLocalEntries; + if ( aDisplayNameProperties[0] >>= aLocalEntries ) + { + for ( const beans::PropertyValue& rConfig : std::as_const(aLocalEntries) ) + { + // PropertyValue name is the locale ("convert" from + // string to canonicalize). + OUString aLocale( LanguageTag( rConfig.Name, true).getBcp47( false)); + // PropertyValue value is the localized value (string in this case). + OUString aName; + rConfig.Value >>= aName; + // Accept 'en' and 'en-...' but prefer 'en-US'. + if (aLocale == "en-US" && !aName.isEmpty()) + aEnglishName = aName; + else if (aEnglishName.isEmpty() && (aLocale == "en" || aLocale.startsWith("en-"))) + aEnglishName = aName; + } + } + } + bool bNeedEnglish = aEnglishName.isEmpty(); + + // get compatibility names + + ::std::vector<ScUnoAddInFuncData::LocalizedName> aCompNames; + + OUString aCompPath(aFuncPropPath + CFGSTR_COMPATIBILITYNAME); + uno::Sequence<OUString> aCompPropNames( &aCompPath, 1 ); + + uno::Sequence<uno::Any> aCompProperties = aAllLocalesConfig.GetProperties( aCompPropNames ); + if ( aCompProperties.getLength() == 1 ) + { + uno::Sequence<beans::PropertyValue> aLocalEntries; + if ( aCompProperties[0] >>= aLocalEntries ) + { + for ( const beans::PropertyValue& rConfig : std::as_const(aLocalEntries) ) + { + // PropertyValue name is the locale ("convert" from + // string to canonicalize). + OUString aLocale( LanguageTag( rConfig.Name, true).getBcp47( false)); + // PropertyValue value is the localized value (string in this case). + OUString aName; + rConfig.Value >>= aName; + if (!aName.isEmpty()) + { + aCompNames.emplace_back( aLocale, aName); + if (bNeedEnglish) + { + // Accept 'en' and 'en-...' but prefer 'en-US'. + if (aLocale == "en-US") + aEnglishName = aName; + else if (aEnglishName.isEmpty() && (aLocale == "en" || aLocale.startsWith("en-"))) + aEnglishName = aName; + } + } + } + } + } + + // get argument info + + std::unique_ptr<ScAddInArgDesc[]> pVisibleArgs; + tools::Long nVisibleCount = 0; + + OUString aArgumentsPath(aFuncPropPath + CFGSTR_PARAMETERS); + + const uno::Sequence<OUString> aArgumentNames = rAddInConfig.GetNodeNames( aArgumentsPath ); + sal_Int32 nArgumentCount = aArgumentNames.getLength(); + if ( nArgumentCount ) + { + // get DisplayName and Description for each argument + uno::Sequence<OUString> aArgPropNames( nArgumentCount * 2 ); + OUString* pPropNameArray = aArgPropNames.getArray(); + + sal_Int32 nIndex = 0; + for ( const OUString& rArgName : aArgumentNames ) + { + OUString aOneArgPath = aArgumentsPath + sSlash + rArgName + sSlash; + + pPropNameArray[nIndex++] = aOneArgPath + + CFGSTR_DISPLAYNAME; + pPropNameArray[nIndex++] = aOneArgPath + + CFGSTR_DESCRIPTION; + } + + uno::Sequence<uno::Any> aArgProperties = rAddInConfig.GetProperties( aArgPropNames ); + if ( aArgProperties.getLength() == aArgPropNames.getLength() ) + { + const OUString* pArgNameArray = aArgumentNames.getConstArray(); + const uno::Any* pPropArray = aArgProperties.getConstArray(); + OUString sDisplayName; + OUString sDescription; + + ScAddInArgDesc aDesc; + aDesc.eType = SC_ADDINARG_NONE; // arg type is not in configuration + aDesc.bOptional = false; + + nVisibleCount = nArgumentCount; + pVisibleArgs.reset(new ScAddInArgDesc[nVisibleCount]); + + nIndex = 0; + for ( sal_Int32 nArgument = 0; nArgument < nArgumentCount; nArgument++ ) + { + pPropArray[nIndex++] >>= sDisplayName; + pPropArray[nIndex++] >>= sDescription; + + aDesc.aInternalName = pArgNameArray[nArgument]; + aDesc.aName = sDisplayName; + aDesc.aDescription = sDescription; + + pVisibleArgs[nArgument] = aDesc; + } + } + } + + OUString sHelpId = aHelpIdGenerator.GetHelpId( pFuncNameArray[nFuncPos] ); + + uno::Reference<reflection::XIdlMethod> xFunc; // remains empty + uno::Any aObject; // also empty + + // create and insert into the array + + ScUnoAddInFuncData* pData = new ScUnoAddInFuncData( + aFuncName, aLocalName, aDescription, + nCategory, sHelpId, + xFunc, aObject, + nVisibleCount, pVisibleArgs.get(), SC_CALLERPOS_NONE ); + + pData->SetCompNames( std::move(aCompNames) ); + + ppFuncData[nFuncPos+nOld].reset(pData); + + pExactHashMap->emplace( + pData->GetOriginalName(), + pData ); + pNameHashMap->emplace( + pData->GetUpperName(), + pData ); + pLocalHashMap->emplace( + pData->GetUpperLocal(), + pData ); + + if (aEnglishName.isEmpty()) + SAL_WARN("sc.core", "no English name for " << aLocalName << " " << aFuncName); + else + { + pEnglishHashMap->emplace( + ScCompiler::GetCharClassEnglish()->uppercase(aEnglishName), + pData ); + } + pData->SetEnglishName(aEnglishName); // takes care of handling empty + } + } + } +} + +void ScUnoAddInCollection::LoadComponent( const ScUnoAddInFuncData& rFuncData ) +{ + const OUString& aFullName = rFuncData.GetOriginalName(); + sal_Int32 nPos = aFullName.lastIndexOf( '.' ); + if ( nPos <= 0 ) + return; + + OUString aServiceName = aFullName.copy( 0, nPos ); + + try + { + uno::Reference<lang::XMultiServiceFactory> xServiceFactory = comphelper::getProcessServiceFactory(); + uno::Reference<uno::XInterface> xInterface( xServiceFactory->createInstance( aServiceName ) ); + + if (xInterface.is()) + UpdateFromAddIn( xInterface, aServiceName ); + } + catch (const uno::Exception &) + { + SAL_WARN ("sc", "Failed to create addin component '" + << aServiceName << "'"); + } +} + +bool ScUnoAddInCollection::GetExcelName( const OUString& rCalcName, + LanguageType eDestLang, OUString& rRetExcelName ) +{ + const ScUnoAddInFuncData* pFuncData = GetFuncData( rCalcName ); + if ( pFuncData ) + return pFuncData->GetExcelName( LanguageTag( eDestLang), rRetExcelName); + return false; +} + +bool ScUnoAddInCollection::GetCalcName( const OUString& rExcelName, OUString& rRetCalcName ) +{ + if (!bInitialized) + Initialize(); + + OUString aUpperCmp = ScGlobal::getCharClass().uppercase(rExcelName); + + for (tools::Long i=0; i<nFuncCount; i++) + { + ScUnoAddInFuncData* pFuncData = ppFuncData[i].get(); + if ( pFuncData ) + { + const ::std::vector<ScUnoAddInFuncData::LocalizedName>& rNames = pFuncData->GetCompNames(); + auto bFound = std::any_of(rNames.begin(), rNames.end(), + [&aUpperCmp](const ScUnoAddInFuncData::LocalizedName& rName) { + return ScGlobal::getCharClass().uppercase( rName.maName ) == aUpperCmp; }); + if (bFound) + { + //TODO: store upper case for comparing? + + // use the first function that has this name for any language + rRetCalcName = pFuncData->GetOriginalName(); + return true; + } + } + } + return false; +} + +static bool IsTypeName( std::u16string_view rName, const uno::Type& rType ) +{ + return rName == rType.getTypeName(); +} + +static bool lcl_ValidReturnType( const uno::Reference<reflection::XIdlClass>& xClass ) +{ + // this must match with ScUnoAddInCall::SetResult + + if ( !xClass.is() ) return false; + + switch (xClass->getTypeClass()) + { + case uno::TypeClass_ANY: // variable type + case uno::TypeClass_ENUM: //TODO: ??? + case uno::TypeClass_BOOLEAN: + case uno::TypeClass_CHAR: + case uno::TypeClass_BYTE: + case uno::TypeClass_SHORT: + case uno::TypeClass_UNSIGNED_SHORT: + case uno::TypeClass_LONG: + case uno::TypeClass_UNSIGNED_LONG: + case uno::TypeClass_FLOAT: + case uno::TypeClass_DOUBLE: + case uno::TypeClass_STRING: + return true; // values or string + + case uno::TypeClass_INTERFACE: + { + // return type XInterface may contain a XVolatileResult + //TODO: XIdlClass needs getType() method! + + OUString sName = xClass->getName(); + return ( + IsTypeName( sName, cppu::UnoType<sheet::XVolatileResult>::get()) || + IsTypeName( sName, cppu::UnoType<uno::XInterface>::get()) ); + } + + default: + { + // nested sequences for arrays + //TODO: XIdlClass needs getType() method! + + OUString sName = xClass->getName(); + return ( + IsTypeName( sName, cppu::UnoType<uno::Sequence< uno::Sequence<sal_Int32> >>::get() ) || + IsTypeName( sName, cppu::UnoType<uno::Sequence< uno::Sequence<double> >>::get() ) || + IsTypeName( sName, cppu::UnoType<uno::Sequence< uno::Sequence<OUString> >>::get() ) || + IsTypeName( sName, cppu::UnoType<uno::Sequence< uno::Sequence<uno::Any> >>::get() ) ); + } + } +} + +static ScAddInArgumentType lcl_GetArgType( const uno::Reference<reflection::XIdlClass>& xClass ) +{ + if (!xClass.is()) + return SC_ADDINARG_NONE; + + uno::TypeClass eType = xClass->getTypeClass(); + + if ( eType == uno::TypeClass_LONG ) //TODO: other integer types? + return SC_ADDINARG_INTEGER; + + if ( eType == uno::TypeClass_DOUBLE ) + return SC_ADDINARG_DOUBLE; + + if ( eType == uno::TypeClass_STRING ) + return SC_ADDINARG_STRING; + + //TODO: XIdlClass needs getType() method! + OUString sName = xClass->getName(); + + if (IsTypeName( sName, cppu::UnoType<uno::Sequence< uno::Sequence<sal_Int32> >>::get() )) + return SC_ADDINARG_INTEGER_ARRAY; + + if (IsTypeName( sName, cppu::UnoType<uno::Sequence< uno::Sequence<double> >>::get() )) + return SC_ADDINARG_DOUBLE_ARRAY; + + if (IsTypeName( sName, cppu::UnoType<uno::Sequence< uno::Sequence<OUString> >>::get() )) + return SC_ADDINARG_STRING_ARRAY; + + if (IsTypeName( sName, cppu::UnoType<uno::Sequence< uno::Sequence<uno::Any> >>::get() )) + return SC_ADDINARG_MIXED_ARRAY; + + if (IsTypeName( sName, cppu::UnoType<uno::Any>::get())) + return SC_ADDINARG_VALUE_OR_ARRAY; + + if (IsTypeName( sName, cppu::UnoType<table::XCellRange>::get())) + return SC_ADDINARG_CELLRANGE; + + if (IsTypeName( sName, cppu::UnoType<beans::XPropertySet>::get())) + return SC_ADDINARG_CALLER; + + if (IsTypeName( sName, cppu::UnoType<uno::Sequence<uno::Any>>::get() )) + return SC_ADDINARG_VARARGS; + + return SC_ADDINARG_NONE; +} + +void ScUnoAddInCollection::ReadFromAddIn( const uno::Reference<uno::XInterface>& xInterface ) +{ + uno::Reference<sheet::XAddIn> xAddIn( xInterface, uno::UNO_QUERY ); + uno::Reference<lang::XServiceName> xName( xInterface, uno::UNO_QUERY ); + if ( !(xAddIn.is() && xName.is()) ) + return; + + // Even if GetUseEnglishFunctionName() would return true, do not set the + // locale to en-US to get English function names as that also would mix in + // English descriptions and parameter names. Also, setting a locale will + // reinitialize the Add-In completely, so switching back and forth isn't a + // good idea either. + xAddIn->setLocale( Application::GetSettings().GetUILanguageTag().getLocale()); + + // Instead, in a second run with 'en-US' obtain English names. + struct FuncNameData + { + OUString aFuncU; + ScUnoAddInFuncData* pData; + }; + std::vector<FuncNameData> aFuncNameData; + + OUString aServiceName( xName->getServiceName() ); + ScUnoAddInHelpIdGenerator aHelpIdGenerator( aServiceName ); + + //TODO: pass XIntrospection to ReadFromAddIn + + uno::Reference<uno::XComponentContext> xContext = comphelper::getProcessComponentContext(); + + uno::Reference<beans::XIntrospection> xIntro = beans::theIntrospection::get( xContext ); + uno::Any aObject; + aObject <<= xAddIn; + uno::Reference<beans::XIntrospectionAccess> xAcc = xIntro->inspect(aObject); + if (!xAcc.is()) + return; + + uno::Sequence< uno::Reference<reflection::XIdlMethod> > aMethods = + xAcc->getMethods( beans::MethodConcept::ALL ); + tools::Long nNewCount = aMethods.getLength(); + if ( !nNewCount ) + return; + + tools::Long nOld = nFuncCount; + nFuncCount = nNewCount+nOld; + if ( nOld ) + { + std::unique_ptr<std::unique_ptr<ScUnoAddInFuncData>[]> ppNew(new std::unique_ptr<ScUnoAddInFuncData>[nFuncCount]); + for (tools::Long i=0; i<nOld; i++) + ppNew[i] = std::move(ppFuncData[i]); + ppFuncData = std::move(ppNew); + } + else + ppFuncData.reset(new std::unique_ptr<ScUnoAddInFuncData>[nFuncCount]); + + //TODO: adjust bucket count? + if ( !pExactHashMap ) + pExactHashMap.reset( new ScAddInHashMap ); + if ( !pNameHashMap ) + pNameHashMap.reset( new ScAddInHashMap ); + if ( !pLocalHashMap ) + pLocalHashMap.reset( new ScAddInHashMap ); + if ( !pEnglishHashMap ) + pEnglishHashMap.reset( new ScAddInHashMap ); + + const uno::Reference<reflection::XIdlMethod>* pArray = aMethods.getConstArray(); + for (tools::Long nFuncPos=0; nFuncPos<nNewCount; nFuncPos++) + { + ppFuncData[nFuncPos+nOld] = nullptr; + + uno::Reference<reflection::XIdlMethod> xFunc = pArray[nFuncPos]; + if (xFunc.is()) + { + // leave out internal functions + uno::Reference<reflection::XIdlClass> xClass = + xFunc->getDeclaringClass(); + bool bSkip = true; + if ( xClass.is() ) + { + //TODO: XIdlClass needs getType() method! + OUString sName = xClass->getName(); + bSkip = ( + IsTypeName( sName, + cppu::UnoType<uno::XInterface>::get()) || + IsTypeName( sName, + cppu::UnoType<lang::XServiceName>::get()) || + IsTypeName( sName, + cppu::UnoType<lang::XServiceInfo>::get()) || + IsTypeName( sName, + cppu::UnoType<sheet::XAddIn>::get()) ); + } + if (!bSkip) + { + uno::Reference<reflection::XIdlClass> xReturn = + xFunc->getReturnType(); + if ( !lcl_ValidReturnType( xReturn ) ) + bSkip = true; + } + if (!bSkip) + { + OUString aFuncU = xFunc->getName(); + + // stored function name: (service name).(function) + OUString aFuncName = aServiceName + "." + aFuncU; + + bool bValid = true; + tools::Long nVisibleCount = 0; + tools::Long nCallerPos = SC_CALLERPOS_NONE; + + uno::Sequence<reflection::ParamInfo> aParams = + xFunc->getParameterInfos(); + tools::Long nParamCount = aParams.getLength(); + const reflection::ParamInfo* pParArr = aParams.getConstArray(); + tools::Long nParamPos; + for (nParamPos=0; nParamPos<nParamCount; nParamPos++) + { + if ( pParArr[nParamPos].aMode != reflection::ParamMode_IN ) + bValid = false; + uno::Reference<reflection::XIdlClass> xParClass = + pParArr[nParamPos].aType; + ScAddInArgumentType eArgType = lcl_GetArgType( xParClass ); + if ( eArgType == SC_ADDINARG_NONE ) + bValid = false; + else if ( eArgType == SC_ADDINARG_CALLER ) + nCallerPos = nParamPos; + else + ++nVisibleCount; + } + if (bValid) + { + sal_uInt16 nCategory = lcl_GetCategory( + xAddIn->getProgrammaticCategoryName( aFuncU ) ); + + OUString sHelpId = aHelpIdGenerator.GetHelpId( aFuncU ); + + OUString aLocalName; + try + { + aLocalName = xAddIn-> + getDisplayFunctionName( aFuncU ); + } + catch(uno::Exception&) + { + aLocalName = "###"; + } + + OUString aDescription; + try + { + aDescription = xAddIn-> + getFunctionDescription( aFuncU ); + } + catch(uno::Exception&) + { + aDescription = "###"; + } + + std::unique_ptr<ScAddInArgDesc[]> pVisibleArgs; + if ( nVisibleCount > 0 ) + { + ScAddInArgDesc aDesc; + pVisibleArgs.reset(new ScAddInArgDesc[nVisibleCount]); + tools::Long nDestPos = 0; + for (nParamPos=0; nParamPos<nParamCount; nParamPos++) + { + uno::Reference<reflection::XIdlClass> xParClass = + pParArr[nParamPos].aType; + ScAddInArgumentType eArgType = lcl_GetArgType( xParClass ); + if ( eArgType != SC_ADDINARG_CALLER ) + { + OUString aArgName; + try + { + aArgName = xAddIn-> + getDisplayArgumentName( aFuncU, nParamPos ); + } + catch(uno::Exception&) + { + aArgName = "###"; + } + OUString aArgDesc; + try + { + aArgDesc = xAddIn-> + getArgumentDescription( aFuncU, nParamPos ); + } + catch(uno::Exception&) + { + aArgDesc = "###"; + } + + bool bOptional = + ( eArgType == SC_ADDINARG_VALUE_OR_ARRAY || + eArgType == SC_ADDINARG_VARARGS ); + + aDesc.eType = eArgType; + aDesc.aName = aArgName; + aDesc.aDescription = aArgDesc; + aDesc.bOptional = bOptional; + //TODO: initialize aInternalName only from config? + aDesc.aInternalName = pParArr[nParamPos].aName; + + pVisibleArgs[nDestPos++] = aDesc; + } + } + OSL_ENSURE( nDestPos==nVisibleCount, "wrong count" ); + } + + ppFuncData[nFuncPos+nOld].reset( new ScUnoAddInFuncData( + aFuncName, aLocalName, aDescription, + nCategory, sHelpId, + xFunc, aObject, + nVisibleCount, pVisibleArgs.get(), nCallerPos ) ); + + ScUnoAddInFuncData* pData = ppFuncData[nFuncPos+nOld].get(); + pExactHashMap->emplace( + pData->GetOriginalName(), + pData ); + pNameHashMap->emplace( + pData->GetUpperName(), + pData ); + pLocalHashMap->emplace( + pData->GetUpperLocal(), + pData ); + + aFuncNameData.push_back({aFuncU, pData}); + } + } + } + } + + const LanguageTag aEnglishLanguageTag(LANGUAGE_ENGLISH_US); + xAddIn->setLocale( aEnglishLanguageTag.getLocale()); + for (const auto& rFunc : aFuncNameData) + { + OUString aEnglishName; + try + { + aEnglishName = xAddIn->getDisplayFunctionName( rFunc.aFuncU ); + } + catch(uno::Exception&) + { + } + if (aEnglishName.isEmpty() + && rFunc.pData->GetExcelName( aEnglishLanguageTag, aEnglishName, false /*bFallbackToAny*/)) + { + // Check our known suffixes and append if not present. Note this + // depends on localization (that should not add such suffix, but..) + // and is really only a last resort. + if (rFunc.pData->GetLocalName().endsWith("_ADD") && !aEnglishName.endsWith("_ADD")) + aEnglishName += "_ADD"; + else if (rFunc.pData->GetLocalName().endsWith("_EXCEL2003") && !aEnglishName.endsWith("_EXCEL2003")) + aEnglishName += "_EXCEL2003"; + SAL_WARN("sc.core", "obtaining English name for " << rFunc.pData->GetLocalName() << " " + << rFunc.pData->GetOriginalName() << " as ExcelName '" << aEnglishName << "'"); + } + SAL_WARN_IF(aEnglishName.isEmpty(), "sc.core", "no English name for " + << rFunc.pData->GetLocalName() << " " << rFunc.pData->GetOriginalName()); + rFunc.pData->SetEnglishName(aEnglishName); // takes care of handling empty + pEnglishHashMap->emplace( rFunc.pData->GetUpperEnglish(), rFunc.pData); + } +} + +static void lcl_UpdateFunctionList( const ScFunctionList& rFunctionList, const ScUnoAddInFuncData& rFuncData, + bool bEnglishFunctionNames ) +{ + // as used in FillFunctionDescFromData + const OUString& aCompare = (bEnglishFunctionNames ? rFuncData.GetUpperEnglish() : rFuncData.GetUpperLocal()); + + sal_uLong nCount = rFunctionList.GetCount(); + for (sal_uLong nPos=0; nPos<nCount; nPos++) + { + const ScFuncDesc* pDesc = rFunctionList.GetFunction( nPos ); + if ( pDesc && pDesc->mxFuncName && *pDesc->mxFuncName == aCompare ) + { + ScUnoAddInCollection::FillFunctionDescFromData( rFuncData, *const_cast<ScFuncDesc*>(pDesc), + bEnglishFunctionNames); + break; + } + } +} + +static const ScAddInArgDesc* lcl_FindArgDesc( const ScUnoAddInFuncData& rFuncData, std::u16string_view rArgIntName ) +{ + tools::Long nArgCount = rFuncData.GetArgumentCount(); + const ScAddInArgDesc* pArguments = rFuncData.GetArguments(); + for (tools::Long nPos=0; nPos<nArgCount; nPos++) + { + if ( pArguments[nPos].aInternalName == rArgIntName ) + return &pArguments[nPos]; + } + return nullptr; +} + +void ScUnoAddInCollection::UpdateFromAddIn( const uno::Reference<uno::XInterface>& xInterface, + std::u16string_view rServiceName ) +{ + const bool bEnglishFunctionNames = SC_MOD()->GetFormulaOptions().GetUseEnglishFuncName(); + uno::Reference<lang::XLocalizable> xLoc( xInterface, uno::UNO_QUERY ); + if ( xLoc.is() ) // optional in new add-ins + xLoc->setLocale( Application::GetSettings().GetUILanguageTag().getLocale()); + + // if function list was already initialized, it must be updated + + ScFunctionList* pFunctionList = nullptr; + if ( ScGlobal::HasStarCalcFunctionList() ) + pFunctionList = ScGlobal::GetStarCalcFunctionList(); + + // only get the function information from Introspection + + uno::Reference<uno::XComponentContext> xContext = comphelper::getProcessComponentContext(); + + uno::Reference<beans::XIntrospection> xIntro = beans::theIntrospection::get(xContext); + uno::Any aObject; + aObject <<= xInterface; + uno::Reference<beans::XIntrospectionAccess> xAcc = xIntro->inspect(aObject); + if (!xAcc.is()) + return; + + const uno::Sequence< uno::Reference<reflection::XIdlMethod> > aMethods = + xAcc->getMethods( beans::MethodConcept::ALL ); + for (const uno::Reference<reflection::XIdlMethod>& xFunc : aMethods) + { + if (xFunc.is()) + { + OUString aFuncU = xFunc->getName(); + + // stored function name: (service name).(function) + OUString aFuncName = OUString::Concat(rServiceName) + "." + aFuncU; + + // internal names are skipped because no FuncData exists + ScUnoAddInFuncData* pOldData = const_cast<ScUnoAddInFuncData*>( GetFuncData( aFuncName ) ); + if ( pOldData ) + { + // Create new (complete) argument info. + // As in ReadFromAddIn, the reflection information is authoritative. + // Local names and descriptions from pOldData are looked up using the + // internal argument name. + + bool bValid = true; + tools::Long nVisibleCount = 0; + tools::Long nCallerPos = SC_CALLERPOS_NONE; + + const uno::Sequence<reflection::ParamInfo> aParams = + xFunc->getParameterInfos(); + tools::Long nParamCount = aParams.getLength(); + const reflection::ParamInfo* pParArr = aParams.getConstArray(); + for (tools::Long nParamPos=0; nParamPos<nParamCount; nParamPos++) + { + if ( pParArr[nParamPos].aMode != reflection::ParamMode_IN ) + bValid = false; + uno::Reference<reflection::XIdlClass> xParClass = + pParArr[nParamPos].aType; + ScAddInArgumentType eArgType = lcl_GetArgType( xParClass ); + if ( eArgType == SC_ADDINARG_NONE ) + bValid = false; + else if ( eArgType == SC_ADDINARG_CALLER ) + nCallerPos = nParamPos; + else + ++nVisibleCount; + } + if (bValid) + { + std::unique_ptr<ScAddInArgDesc[]> pVisibleArgs; + if ( nVisibleCount > 0 ) + { + ScAddInArgDesc aDesc; + pVisibleArgs.reset(new ScAddInArgDesc[nVisibleCount]); + tools::Long nDestPos = 0; + for (const auto& rParam : aParams) + { + uno::Reference<reflection::XIdlClass> xParClass = + rParam.aType; + ScAddInArgumentType eArgType = lcl_GetArgType( xParClass ); + if ( eArgType != SC_ADDINARG_CALLER ) + { + const ScAddInArgDesc* pOldArgDesc = + lcl_FindArgDesc( *pOldData, rParam.aName ); + if ( pOldArgDesc ) + { + aDesc.aName = pOldArgDesc->aName; + aDesc.aDescription = pOldArgDesc->aDescription; + } + else + aDesc.aName = aDesc.aDescription = "###"; + + bool bOptional = + ( eArgType == SC_ADDINARG_VALUE_OR_ARRAY || + eArgType == SC_ADDINARG_VARARGS ); + + aDesc.eType = eArgType; + aDesc.bOptional = bOptional; + //TODO: initialize aInternalName only from config? + aDesc.aInternalName = rParam.aName; + + pVisibleArgs[nDestPos++] = aDesc; + } + } + OSL_ENSURE( nDestPos==nVisibleCount, "wrong count" ); + } + + pOldData->SetFunction( xFunc, aObject ); + pOldData->SetArguments( nVisibleCount, pVisibleArgs.get() ); + pOldData->SetCallerPos( nCallerPos ); + + if ( pFunctionList ) + lcl_UpdateFunctionList( *pFunctionList, *pOldData, bEnglishFunctionNames ); + } + } + } + } +} + +OUString ScUnoAddInCollection::FindFunction( const OUString& rUpperName, bool bLocalFirst ) +{ + if (!bInitialized) + Initialize(); + + if (nFuncCount == 0) + return OUString(); + + if ( bLocalFirst ) + { + // Only scan local names (used for entering formulas). + + ScAddInHashMap::const_iterator iLook( pLocalHashMap->find( rUpperName ) ); + if ( iLook != pLocalHashMap->end() ) + return iLook->second->GetOriginalName(); + } + else + { + // First scan international programmatic names (used when calling a + // function). + + ScAddInHashMap::const_iterator iLook( pNameHashMap->find( rUpperName ) ); + if ( iLook != pNameHashMap->end() ) + return iLook->second->GetOriginalName(); + + // Then scan English names (as FunctionAccess API could expect). + + iLook = pEnglishHashMap->find( rUpperName ); + if ( iLook != pEnglishHashMap->end() ) + return iLook->second->GetOriginalName(); + + // After that, scan all local names; either to allow replacing old + // AddIns with Uno, or for functions where the AddIn did not provide an + // English name. + + iLook = pLocalHashMap->find( rUpperName ); + if ( iLook != pLocalHashMap->end() ) + return iLook->second->GetOriginalName(); + } + + return OUString(); +} + +const ScUnoAddInFuncData* ScUnoAddInCollection::GetFuncData( const OUString& rName, bool bComplete ) +{ + if (!bInitialized) + Initialize(); + + // rName must be the exact internal name + + ScAddInHashMap::const_iterator iLook( pExactHashMap->find( rName ) ); + if ( iLook != pExactHashMap->end() ) + { + const ScUnoAddInFuncData* pFuncData = iLook->second; + + if ( bComplete && !pFuncData->GetFunction().is() ) //TODO: extra flag? + LoadComponent( *pFuncData ); + + return pFuncData; + } + + return nullptr; +} + +const ScUnoAddInFuncData* ScUnoAddInCollection::GetFuncData( tools::Long nIndex ) +{ + if (!bInitialized) + Initialize(); + + if (nIndex < nFuncCount) + return ppFuncData[nIndex].get(); + return nullptr; +} + +void ScUnoAddInCollection::LocalizeString( OUString& rName ) +{ + if (!bInitialized) + Initialize(); + + // modify rName - input: exact name + + ScAddInHashMap::const_iterator iLook( pExactHashMap->find( rName ) ); + if ( iLook != pExactHashMap->end() ) + rName = iLook->second->GetUpperLocal(); //TODO: upper? +} + +tools::Long ScUnoAddInCollection::GetFuncCount() +{ + if (!bInitialized) + Initialize(); + + return nFuncCount; +} + +bool ScUnoAddInCollection::FillFunctionDesc( tools::Long nFunc, ScFuncDesc& rDesc, bool bEnglishFunctionNames ) +{ + if (!bInitialized) + Initialize(); + + if (nFunc >= nFuncCount || !ppFuncData[nFunc]) + return false; + + const ScUnoAddInFuncData& rFuncData = *ppFuncData[nFunc]; + + return FillFunctionDescFromData( rFuncData, rDesc, bEnglishFunctionNames ); +} + +bool ScUnoAddInCollection::FillFunctionDescFromData( const ScUnoAddInFuncData& rFuncData, ScFuncDesc& rDesc, + bool bEnglishFunctionNames ) +{ + rDesc.Clear(); + + bool bIncomplete = !rFuncData.GetFunction().is(); //TODO: extra flag? + + tools::Long nArgCount = rFuncData.GetArgumentCount(); + if ( nArgCount > SAL_MAX_UINT16 ) + return false; + + if ( bIncomplete ) + nArgCount = 0; // if incomplete, fill without argument info (no wrong order) + + // nFIndex is set from outside + + rDesc.mxFuncName = (bEnglishFunctionNames ? rFuncData.GetUpperEnglish() : rFuncData.GetUpperLocal()); + rDesc.nCategory = rFuncData.GetCategory(); + rDesc.sHelpId = rFuncData.GetHelpId(); + + OUString aDesc = rFuncData.GetDescription(); + if (aDesc.isEmpty()) + aDesc = rFuncData.GetLocalName(); // use name if no description is available + rDesc.mxFuncDesc = aDesc ; + + // AddInArgumentType_CALLER is already left out in FuncData + + rDesc.nArgCount = static_cast<sal_uInt16>(nArgCount); + if ( nArgCount ) + { + bool bMultiple = false; + const ScAddInArgDesc* pArgs = rFuncData.GetArguments(); + + rDesc.maDefArgNames.clear(); + rDesc.maDefArgNames.resize(nArgCount); + rDesc.maDefArgDescs.clear(); + rDesc.maDefArgDescs.resize(nArgCount); + rDesc.pDefArgFlags = new ScFuncDesc::ParameterFlags[nArgCount]; + for ( tools::Long nArg=0; nArg<nArgCount; nArg++ ) + { + rDesc.maDefArgNames[nArg] = pArgs[nArg].aName; + rDesc.maDefArgDescs[nArg] = pArgs[nArg].aDescription; + rDesc.pDefArgFlags[nArg].bOptional = pArgs[nArg].bOptional; + + // no empty names... + if ( rDesc.maDefArgNames[nArg].isEmpty() ) + { + OUString aDefName = "arg" + OUString::number( nArg+1 ); + rDesc.maDefArgNames[nArg] = aDefName; + } + + // last argument repeated? + if ( nArg+1 == nArgCount && ( pArgs[nArg].eType == SC_ADDINARG_VARARGS ) ) + bMultiple = true; + } + + if ( bMultiple ) + rDesc.nArgCount += VAR_ARGS - 1; // VAR_ARGS means just one repeated arg + } + + rDesc.bIncomplete = bIncomplete; + + return true; +} + +ScUnoAddInCall::ScUnoAddInCall( ScDocument& rDoc, ScUnoAddInCollection& rColl, const OUString& rName, + tools::Long nParamCount ) : + mrDoc( rDoc ), + bValidCount( false ), + nErrCode( FormulaError::NoCode ), // before function was called + bHasString( true ), + fValue( 0.0 ), + xMatrix( nullptr ) +{ + pFuncData = rColl.GetFuncData( rName, true ); // need fully initialized data + OSL_ENSURE( pFuncData, "Function Data missing" ); + if ( !pFuncData ) + return; + + tools::Long nDescCount = pFuncData->GetArgumentCount(); + const ScAddInArgDesc* pArgs = pFuncData->GetArguments(); + + // is aVarArg sequence needed? + if ( nParamCount >= nDescCount && nDescCount > 0 && + pArgs[nDescCount-1].eType == SC_ADDINARG_VARARGS ) + { + tools::Long nVarCount = nParamCount - ( nDescCount - 1 ); // size of last argument + aVarArg.realloc( nVarCount ); + bValidCount = true; + } + else if ( nParamCount <= nDescCount ) + { + // all args behind nParamCount must be optional + bValidCount = true; + for (tools::Long i=nParamCount; i<nDescCount; i++) + if ( !pArgs[i].bOptional ) + bValidCount = false; + } + // else invalid (too many arguments) + + if ( bValidCount ) + aArgs.realloc( nDescCount ); // sequence must always match function signature +} + +ScUnoAddInCall::~ScUnoAddInCall() +{ + // pFuncData is deleted with ScUnoAddInCollection +} + +ScAddInArgumentType ScUnoAddInCall::GetArgType( tools::Long nPos ) +{ + if ( pFuncData ) + { + tools::Long nCount = pFuncData->GetArgumentCount(); + const ScAddInArgDesc* pArgs = pFuncData->GetArguments(); + + // if last arg is sequence, use "any" type + if ( nCount > 0 && nPos >= nCount-1 && pArgs[nCount-1].eType == SC_ADDINARG_VARARGS ) + return SC_ADDINARG_VALUE_OR_ARRAY; + + if ( nPos < nCount ) + return pArgs[nPos].eType; + } + return SC_ADDINARG_VALUE_OR_ARRAY; //TODO: error code !!!! +} + +bool ScUnoAddInCall::NeedsCaller() const +{ + return pFuncData && pFuncData->GetCallerPos() != SC_CALLERPOS_NONE; +} + +void ScUnoAddInCall::SetCaller( const uno::Reference<uno::XInterface>& rInterface ) +{ + xCaller = rInterface; +} + +void ScUnoAddInCall::SetCallerFromObjectShell( const SfxObjectShell* pObjSh ) +{ + if (pObjSh) + { + uno::Reference<uno::XInterface> xInt( pObjSh->GetBaseModel(), uno::UNO_QUERY ); + SetCaller( xInt ); + } +} + +void ScUnoAddInCall::SetParam( tools::Long nPos, const uno::Any& rValue ) +{ + if ( !pFuncData ) + return; + + tools::Long nCount = pFuncData->GetArgumentCount(); + const ScAddInArgDesc* pArgs = pFuncData->GetArguments(); + if ( nCount > 0 && nPos >= nCount-1 && pArgs[nCount-1].eType == SC_ADDINARG_VARARGS ) + { + tools::Long nVarPos = nPos-(nCount-1); + if ( nVarPos < aVarArg.getLength() ) + aVarArg.getArray()[nVarPos] = rValue; + else + { + OSL_FAIL("wrong argument number"); + } + } + else if ( nPos < aArgs.getLength() ) + aArgs.getArray()[nPos] = rValue; + else + { + OSL_FAIL("wrong argument number"); + } +} + +void ScUnoAddInCall::ExecuteCall() +{ + if ( !pFuncData ) + return; + + tools::Long nCount = pFuncData->GetArgumentCount(); + const ScAddInArgDesc* pArgs = pFuncData->GetArguments(); + if ( nCount > 0 && pArgs[nCount-1].eType == SC_ADDINARG_VARARGS ) + { + // insert aVarArg as last argument + //TODO: after inserting caller (to prevent copying twice)? + + OSL_ENSURE( aArgs.getLength() == nCount, "wrong argument count" ); + aArgs.getArray()[nCount-1] <<= aVarArg; + } + + if ( pFuncData->GetCallerPos() != SC_CALLERPOS_NONE ) + { + uno::Any aCallerAny; + aCallerAny <<= xCaller; + + tools::Long nUserLen = aArgs.getLength(); + tools::Long nCallPos = pFuncData->GetCallerPos(); + if (nCallPos>nUserLen) // should not happen + { + OSL_FAIL("wrong CallPos"); + nCallPos = nUserLen; + } + + tools::Long nDestLen = nUserLen + 1; + uno::Sequence<uno::Any> aRealArgs( nDestLen ); + uno::Any* pDest = aRealArgs.getArray(); + + pDest = std::copy_n(std::cbegin(aArgs), nCallPos, pDest); + *pDest = aCallerAny; + std::copy(std::next(std::cbegin(aArgs), nCallPos), std::cend(aArgs), std::next(pDest)); + + ExecuteCallWithArgs( aRealArgs ); + } + else + ExecuteCallWithArgs( aArgs ); +} + +void ScUnoAddInCall::ExecuteCallWithArgs(uno::Sequence<uno::Any>& rCallArgs) +{ + // rCallArgs may not match argument descriptions (because of caller) + + uno::Reference<reflection::XIdlMethod> xFunction; + uno::Any aObject; + if ( pFuncData ) + { + xFunction = pFuncData->GetFunction(); + aObject = pFuncData->GetObject(); + } + + if ( !xFunction.is() ) + return; + + uno::Any aAny; + nErrCode = FormulaError::NONE; + + try + { + aAny = xFunction->invoke( aObject, rCallArgs ); + } + catch(lang::IllegalArgumentException&) + { + nErrCode = FormulaError::IllegalArgument; + } + catch(const reflection::InvocationTargetException& rWrapped) + { + if ( rWrapped.TargetException.getValueType().equals( + cppu::UnoType<lang::IllegalArgumentException>::get()) ) + nErrCode = FormulaError::IllegalArgument; + else if ( rWrapped.TargetException.getValueType().equals( + cppu::UnoType<sheet::NoConvergenceException>::get()) ) + nErrCode = FormulaError::NoConvergence; + else + nErrCode = FormulaError::NoValue; + } + catch(uno::Exception&) + { + nErrCode = FormulaError::NoValue; + } + + if (nErrCode == FormulaError::NONE) + SetResult( aAny ); // convert result to Calc types +} + +template <typename T> +static sal_Int32 lcl_GetMaxColCount(const uno::Sequence< uno::Sequence<T> >* pRowSeq) +{ + if (!pRowSeq->hasElements()) + return 0; + + auto pRow = std::max_element(pRowSeq->begin(), pRowSeq->end(), + [](const uno::Sequence<T>& a, const uno::Sequence<T>& b) { + return a.getLength() < b.getLength(); }); + return pRow->getLength(); +} + +void ScUnoAddInCall::SetResult( const uno::Any& rNewRes ) +{ + nErrCode = FormulaError::NONE; + xVarRes = nullptr; + + // Reflection* pRefl = rNewRes.getReflection(); + + uno::TypeClass eClass = rNewRes.getValueTypeClass(); + const uno::Type& aType = rNewRes.getValueType(); + switch (eClass) + { + case uno::TypeClass_VOID: + nErrCode = FormulaError::NotAvailable; // #NA + break; + + case uno::TypeClass_ENUM: + case uno::TypeClass_BOOLEAN: + case uno::TypeClass_CHAR: + case uno::TypeClass_BYTE: + case uno::TypeClass_SHORT: + case uno::TypeClass_UNSIGNED_SHORT: + case uno::TypeClass_LONG: + case uno::TypeClass_UNSIGNED_LONG: + case uno::TypeClass_FLOAT: + case uno::TypeClass_DOUBLE: + { + uno::TypeClass eMyClass; + ScApiTypeConversion::ConvertAnyToDouble( fValue, eMyClass, rNewRes); + bHasString = false; + } + break; + + case uno::TypeClass_STRING: + { + rNewRes >>= aString; + bHasString = true; + } + break; + + case uno::TypeClass_INTERFACE: + { + //TODO: directly extract XVolatileResult from any? + uno::Reference<uno::XInterface> xInterface; + rNewRes >>= xInterface; + if ( xInterface.is() ) + xVarRes.set( xInterface, uno::UNO_QUERY ); + + if (!xVarRes.is()) + nErrCode = FormulaError::NoValue; // unknown interface + } + break; + + default: + if ( aType.equals( cppu::UnoType<uno::Sequence< uno::Sequence<sal_Int32> >>::get() ) ) + { + const uno::Sequence< uno::Sequence<sal_Int32> >* pRowSeq = nullptr; + + //TODO: use pointer from any! + uno::Sequence< uno::Sequence<sal_Int32> > aSequence; + if ( rNewRes >>= aSequence ) + pRowSeq = &aSequence; + + if ( pRowSeq ) + { + sal_Int32 nRowCount = pRowSeq->getLength(); + sal_Int32 nMaxColCount = lcl_GetMaxColCount(pRowSeq); + if ( nMaxColCount && nRowCount ) + { + const uno::Sequence<sal_Int32>* pRowArr = pRowSeq->getConstArray(); + xMatrix = new ScMatrix( + static_cast<SCSIZE>(nMaxColCount), + static_cast<SCSIZE>(nRowCount), 0.0); + for (sal_Int32 nRow=0; nRow<nRowCount; nRow++) + { + sal_Int32 nColCount = pRowArr[nRow].getLength(); + const sal_Int32* pColArr = pRowArr[nRow].getConstArray(); + for (sal_Int32 nCol=0; nCol<nColCount; nCol++) + xMatrix->PutDouble( pColArr[nCol], + static_cast<SCSIZE>(nCol), + static_cast<SCSIZE>(nRow) ); + for (sal_Int32 nCol=nColCount; nCol<nMaxColCount; nCol++) + xMatrix->PutDouble( 0.0, + static_cast<SCSIZE>(nCol), + static_cast<SCSIZE>(nRow) ); + } + } + } + } + else if ( aType.equals( cppu::UnoType<uno::Sequence< uno::Sequence<double> >>::get() ) ) + { + const uno::Sequence< uno::Sequence<double> >* pRowSeq = nullptr; + + //TODO: use pointer from any! + uno::Sequence< uno::Sequence<double> > aSequence; + if ( rNewRes >>= aSequence ) + pRowSeq = &aSequence; + + if ( pRowSeq ) + { + sal_Int32 nRowCount = pRowSeq->getLength(); + sal_Int32 nMaxColCount = lcl_GetMaxColCount(pRowSeq); + if ( nMaxColCount && nRowCount ) + { + const uno::Sequence<double>* pRowArr = pRowSeq->getConstArray(); + xMatrix = new ScMatrix( + static_cast<SCSIZE>(nMaxColCount), + static_cast<SCSIZE>(nRowCount), 0.0); + for (sal_Int32 nRow=0; nRow<nRowCount; nRow++) + { + sal_Int32 nColCount = pRowArr[nRow].getLength(); + const double* pColArr = pRowArr[nRow].getConstArray(); + for (sal_Int32 nCol=0; nCol<nColCount; nCol++) + xMatrix->PutDouble( pColArr[nCol], + static_cast<SCSIZE>(nCol), + static_cast<SCSIZE>(nRow) ); + for (sal_Int32 nCol=nColCount; nCol<nMaxColCount; nCol++) + xMatrix->PutDouble( 0.0, + static_cast<SCSIZE>(nCol), + static_cast<SCSIZE>(nRow) ); + } + } + } + } + else if ( aType.equals( cppu::UnoType<uno::Sequence< uno::Sequence<OUString> >>::get() ) ) + { + const uno::Sequence< uno::Sequence<OUString> >* pRowSeq = nullptr; + + //TODO: use pointer from any! + uno::Sequence< uno::Sequence<OUString> > aSequence; + if ( rNewRes >>= aSequence ) + pRowSeq = &aSequence; + + if ( pRowSeq ) + { + sal_Int32 nRowCount = pRowSeq->getLength(); + sal_Int32 nMaxColCount = lcl_GetMaxColCount(pRowSeq); + if ( nMaxColCount && nRowCount ) + { + const uno::Sequence<OUString>* pRowArr = pRowSeq->getConstArray(); + xMatrix = new ScMatrix( + static_cast<SCSIZE>(nMaxColCount), + static_cast<SCSIZE>(nRowCount), 0.0); + for (sal_Int32 nRow=0; nRow<nRowCount; nRow++) + { + sal_Int32 nColCount = pRowArr[nRow].getLength(); + const OUString* pColArr = pRowArr[nRow].getConstArray(); + for (sal_Int32 nCol=0; nCol<nColCount; nCol++) + { + xMatrix->PutString( + mrDoc.GetSharedStringPool().intern(pColArr[nCol]), + static_cast<SCSIZE>(nCol), static_cast<SCSIZE>(nRow)); + } + for (sal_Int32 nCol=nColCount; nCol<nMaxColCount; nCol++) + { + xMatrix->PutString( + svl::SharedString::getEmptyString(), + static_cast<SCSIZE>(nCol), static_cast<SCSIZE>(nRow)); + } + } + } + } + } + else if ( aType.equals( cppu::UnoType<uno::Sequence< uno::Sequence<uno::Any> >>::get() ) ) + { + xMatrix = ScSequenceToMatrix::CreateMixedMatrix( rNewRes ); + } + + if (!xMatrix) // no array found + nErrCode = FormulaError::NoValue; //TODO: code for error in return type??? + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/addinhelpid.cxx b/sc/source/core/tool/addinhelpid.cxx new file mode 100644 index 0000000000..9c44e269d0 --- /dev/null +++ b/sc/source/core/tool/addinhelpid.cxx @@ -0,0 +1,210 @@ +/* -*- 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 <addinhelpid.hxx> +#include <helpids.h> +#include <o3tl/string_view.hxx> + +// A struct containing the built-in function name and the built-in help ID. +struct ScUnoAddInHelpId +{ + const char* pFuncName; + OUString sHelpId; +}; + +// Help IDs for Analysis AddIn. MUST BE SORTED for binary search. +const ScUnoAddInHelpId pAnalysisHelpIds[] = +{ + { "getAccrint" , HID_AAI_FUNC_ACCRINT }, + { "getAccrintm" , HID_AAI_FUNC_ACCRINTM }, + { "getAmordegrc" , HID_AAI_FUNC_AMORDEGRC }, + { "getAmorlinc" , HID_AAI_FUNC_AMORLINC }, + { "getBesseli" , HID_AAI_FUNC_BESSELI }, + { "getBesselj" , HID_AAI_FUNC_BESSELJ }, + { "getBesselk" , HID_AAI_FUNC_BESSELK }, + { "getBessely" , HID_AAI_FUNC_BESSELY }, + { "getBin2Dec" , HID_AAI_FUNC_BIN2DEC }, + { "getBin2Hex" , HID_AAI_FUNC_BIN2HEX }, + { "getBin2Oct" , HID_AAI_FUNC_BIN2OCT }, + { "getComplex" , HID_AAI_FUNC_COMPLEX }, + { "getConvert" , HID_AAI_FUNC_CONVERT }, + { "getCoupdaybs" , HID_AAI_FUNC_COUPDAYBS }, + { "getCoupdays" , HID_AAI_FUNC_COUPDAYS }, + { "getCoupdaysnc" , HID_AAI_FUNC_COUPDAYSNC }, + { "getCoupncd" , HID_AAI_FUNC_COUPNCD }, + { "getCoupnum" , HID_AAI_FUNC_COUPNUM }, + { "getCouppcd" , HID_AAI_FUNC_COUPPCD }, + { "getCumipmt" , HID_AAI_FUNC_CUMIPMT }, + { "getCumprinc" , HID_AAI_FUNC_CUMPRINC }, + { "getDec2Bin" , HID_AAI_FUNC_DEC2BIN }, + { "getDec2Hex" , HID_AAI_FUNC_DEC2HEX }, + { "getDec2Oct" , HID_AAI_FUNC_DEC2OCT }, + { "getDelta" , HID_AAI_FUNC_DELTA }, + { "getDisc" , HID_AAI_FUNC_DISC }, + { "getDollarde" , HID_AAI_FUNC_DOLLARDE }, + { "getDollarfr" , HID_AAI_FUNC_DOLLARFR }, + { "getDuration" , HID_AAI_FUNC_DURATION }, + { "getEdate" , HID_AAI_FUNC_EDATE }, + { "getEffect" , HID_AAI_FUNC_EFFECT }, + { "getEomonth" , HID_AAI_FUNC_EOMONTH }, + { "getErf" , HID_AAI_FUNC_ERF }, + { "getErfc" , HID_AAI_FUNC_ERFC }, + { "getFactdouble" , HID_AAI_FUNC_FACTDOUBLE }, + { "getFvschedule" , HID_AAI_FUNC_FVSCHEDULE }, + { "getGcd" , HID_AAI_FUNC_GCD }, + { "getGestep" , HID_AAI_FUNC_GESTEP }, + { "getHex2Bin" , HID_AAI_FUNC_HEX2BIN }, + { "getHex2Dec" , HID_AAI_FUNC_HEX2DEC }, + { "getHex2Oct" , HID_AAI_FUNC_HEX2OCT }, + { "getImabs" , HID_AAI_FUNC_IMABS }, + { "getImaginary" , HID_AAI_FUNC_IMAGINARY }, + { "getImargument" , HID_AAI_FUNC_IMARGUMENT }, + { "getImconjugate" , HID_AAI_FUNC_IMCONJUGATE }, + { "getImcos" , HID_AAI_FUNC_IMCOS }, + { "getImcosh" , HID_AAI_FUNC_IMCOSH }, + { "getImcot" , HID_AAI_FUNC_IMCOT }, + { "getImcsc" , HID_AAI_FUNC_IMCSC }, + { "getImcsch" , HID_AAI_FUNC_IMCSCH }, + { "getImdiv" , HID_AAI_FUNC_IMDIV }, + { "getImexp" , HID_AAI_FUNC_IMEXP }, + { "getImln" , HID_AAI_FUNC_IMLN }, + { "getImlog10" , HID_AAI_FUNC_IMLOG10 }, + { "getImlog2" , HID_AAI_FUNC_IMLOG2 }, + { "getImpower" , HID_AAI_FUNC_IMPOWER }, + { "getImproduct" , HID_AAI_FUNC_IMPRODUCT }, + { "getImreal" , HID_AAI_FUNC_IMREAL }, + { "getImsec" , HID_AAI_FUNC_IMSEC }, + { "getImsech" , HID_AAI_FUNC_IMSECH }, + { "getImsin" , HID_AAI_FUNC_IMSIN }, + { "getImsinh" , HID_AAI_FUNC_IMSINH }, + { "getImsqrt" , HID_AAI_FUNC_IMSQRT }, + { "getImsub" , HID_AAI_FUNC_IMSUB }, + { "getImsum" , HID_AAI_FUNC_IMSUM }, + { "getImtan" , HID_AAI_FUNC_IMTAN }, + { "getIntrate" , HID_AAI_FUNC_INTRATE }, + { "getIseven" , HID_AAI_FUNC_ISEVEN }, + { "getIsodd" , HID_AAI_FUNC_ISODD }, + { "getLcm" , HID_AAI_FUNC_LCM }, + { "getMduration" , HID_AAI_FUNC_MDURATION }, + { "getMround" , HID_AAI_FUNC_MROUND }, + { "getMultinomial" , HID_AAI_FUNC_MULTINOMIAL }, + { "getNetworkdays" , HID_AAI_FUNC_NETWORKDAYS }, + { "getNominal" , HID_AAI_FUNC_NOMINAL }, + { "getOct2Bin" , HID_AAI_FUNC_OCT2BIN }, + { "getOct2Dec" , HID_AAI_FUNC_OCT2DEZ }, + { "getOct2Hex" , HID_AAI_FUNC_OCT2HEX }, + { "getOddfprice" , HID_AAI_FUNC_ODDFPRICE }, + { "getOddfyield" , HID_AAI_FUNC_ODDFYIELD }, + { "getOddlprice" , HID_AAI_FUNC_ODDLPRICE }, + { "getOddlyield" , HID_AAI_FUNC_ODDLYIELD }, + { "getPrice" , HID_AAI_FUNC_PRICE }, + { "getPricedisc" , HID_AAI_FUNC_PRICEDISC }, + { "getPricemat" , HID_AAI_FUNC_PRICEMAT }, + { "getQuotient" , HID_AAI_FUNC_QUOTIENT }, + { "getRandbetween" , HID_AAI_FUNC_RANDBETWEEN }, + { "getReceived" , HID_AAI_FUNC_RECEIVED }, + { "getSeriessum" , HID_AAI_FUNC_SERIESSUM }, + { "getSqrtpi" , HID_AAI_FUNC_SQRTPI }, + { "getTbilleq" , HID_AAI_FUNC_TBILLEQ }, + { "getTbillprice" , HID_AAI_FUNC_TBILLPRICE }, + { "getTbillyield" , HID_AAI_FUNC_TBILLYIELD }, + { "getWeeknum" , HID_AAI_FUNC_WEEKNUM }, + { "getWorkday" , HID_AAI_FUNC_WORKDAY }, + { "getXirr" , HID_AAI_FUNC_XIRR }, + { "getXnpv" , HID_AAI_FUNC_XNPV }, + { "getYearfrac" , HID_AAI_FUNC_YEARFRAC }, + { "getYield" , HID_AAI_FUNC_YIELD }, + { "getYielddisc" , HID_AAI_FUNC_YIELDDISC }, + { "getYieldmat" , HID_AAI_FUNC_YIELDMAT } +}; + +// Help IDs for DateFunc AddIn. MUST BE SORTED for binary search. +const ScUnoAddInHelpId pDateFuncHelpIds[] = +{ + { "getDaysInMonth" , HID_DAI_FUNC_DAYSINMONTH }, + { "getDaysInYear" , HID_DAI_FUNC_DAYSINYEAR }, + { "getDiffMonths" , HID_DAI_FUNC_DIFFMONTHS }, + { "getDiffWeeks" , HID_DAI_FUNC_DIFFWEEKS }, + { "getDiffYears" , HID_DAI_FUNC_DIFFYEARS }, + { "getRot13" , HID_DAI_FUNC_ROT13 }, + { "getWeeksInYear" , HID_DAI_FUNC_WEEKSINYEAR } +}; + +// Help IDs for Pricing AddIn. MUST BE SORTED for binary search. +const ScUnoAddInHelpId pPricingFuncHelpIds[] = +{ + { "getOptBarrier" , HID_PAI_FUNC_OPT_BARRIER }, + { "getOptProbHit" , HID_PAI_FUNC_OPT_PROB_HIT }, + { "getOptProbInMoney" , HID_PAI_FUNC_OPT_PROB_INMONEY }, + { "getOptTouch" , HID_PAI_FUNC_OPT_TOUCH } +}; + +ScUnoAddInHelpIdGenerator::ScUnoAddInHelpIdGenerator( std::u16string_view rServiceName ) +{ + SetServiceName( rServiceName ); +} + +void ScUnoAddInHelpIdGenerator::SetServiceName( std::u16string_view rServiceName ) +{ + pCurrHelpIds = nullptr; + sal_uInt32 nSize = 0; + + if ( rServiceName == u"com.sun.star.sheet.addin.Analysis" ) + { + pCurrHelpIds = pAnalysisHelpIds; + nSize = sizeof( pAnalysisHelpIds ); + } + else if ( rServiceName == u"com.sun.star.sheet.addin.DateFunctions" ) + { + pCurrHelpIds = pDateFuncHelpIds; + nSize = sizeof( pDateFuncHelpIds ); + } + else if ( rServiceName == u"com.sun.star.sheet.addin.PricingFunctions") + { + pCurrHelpIds = pPricingFuncHelpIds; + nSize = sizeof( pPricingFuncHelpIds); + } + + nArrayCount = nSize / sizeof( ScUnoAddInHelpId ); +} + +OUString ScUnoAddInHelpIdGenerator::GetHelpId( std::u16string_view rFuncName ) const +{ + if( !pCurrHelpIds || !nArrayCount ) + return {}; + + const ScUnoAddInHelpId* pFirst = pCurrHelpIds; + const ScUnoAddInHelpId* pLast = pCurrHelpIds + nArrayCount - 1; + + while( pFirst <= pLast ) + { + const ScUnoAddInHelpId* pMiddle = pFirst + (pLast - pFirst) / 2; + sal_Int32 nResult = o3tl::compareToAscii( rFuncName, pMiddle->pFuncName ); + if( !nResult ) + return pMiddle->sHelpId; + else if( nResult < 0 ) + pLast = pMiddle - 1; + else + pFirst = pMiddle + 1; + } + + return {}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/addinlis.cxx b/sc/source/core/tool/addinlis.cxx new file mode 100644 index 0000000000..f8a236780e --- /dev/null +++ b/sc/source/core/tool/addinlis.cxx @@ -0,0 +1,135 @@ +/* -*- 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 <sfx2/objsh.hxx> +#include <utility> +#include <vcl/svapp.hxx> + +#include <addinlis.hxx> +#include <miscuno.hxx> +#include <document.hxx> +#include <docsh.hxx> +#include <brdcst.hxx> + +#include <com/sun/star/sheet/XVolatileResult.hpp> + +using namespace com::sun::star; + +SC_SIMPLE_SERVICE_INFO( ScAddInListener, "ScAddInListener", "stardiv.one.sheet.AddInListener" ) + +::std::vector<rtl::Reference<ScAddInListener>> ScAddInListener::aAllListeners; + +ScAddInListener* ScAddInListener::CreateListener( + const uno::Reference<sheet::XVolatileResult>& xVR, ScDocument* pDoc ) +{ + rtl::Reference<ScAddInListener> xNew = new ScAddInListener( xVR, pDoc ); + + aAllListeners.push_back( xNew ); + + if ( xVR.is() ) + xVR->addResultListener( xNew ); // after at least 1 ref exists! + + return xNew.get(); +} + +ScAddInListener::ScAddInListener( uno::Reference<sheet::XVolatileResult> xVR, ScDocument* pDoc ) : + xVolRes(std::move( xVR )), + pDocs( new ScAddInDocs ) +{ + pDocs->insert( pDoc ); +} + +ScAddInListener::~ScAddInListener() +{ +} + +ScAddInListener* ScAddInListener::Get( const uno::Reference<sheet::XVolatileResult>& xVR ) +{ + ScAddInListener* pLst = nullptr; + sheet::XVolatileResult* pComp = xVR.get(); + + for (auto const& listener : aAllListeners) + { + if ( pComp == listener->xVolRes.get() ) + { + pLst = listener.get(); + break; + } + } + return pLst; +} + +//TODO: move to some container object? +void ScAddInListener::RemoveDocument( ScDocument* pDocumentP ) +{ + auto iter = aAllListeners.begin(); + while(iter != aAllListeners.end()) + { + ScAddInDocs* p = (*iter)->pDocs.get(); + ScAddInDocs::iterator iter2 = p->find( pDocumentP ); + if( iter2 != p->end() ) + { + p->erase( iter2 ); + if ( p->empty() ) + { + if ( (*iter)->xVolRes.is() ) + (*iter)->xVolRes->removeResultListener( *iter ); + + iter = aAllListeners.erase( iter ); + continue; + } + } + ++iter; + } +} + +// XResultListener + +void SAL_CALL ScAddInListener::modified( const css::sheet::ResultEvent& aEvent ) +{ + SolarMutexGuard aGuard; //TODO: or generate a UserEvent + + aResult = aEvent.Value; // store result + + // notify document of changes + + Broadcast( ScHint(SfxHintId::ScDataChanged, ScAddress()) ); + + for (auto const& pDoc : *pDocs) + { + pDoc->TrackFormulas(); + pDoc->GetDocumentShell()->Broadcast( SfxHint( SfxHintId::ScDataChanged ) ); + } +} + +// XEventListener + +void SAL_CALL ScAddInListener::disposing( const css::lang::EventObject& /* Source */ ) +{ + // hold a ref so this is not deleted at removeResultListener + uno::Reference<sheet::XResultListener> xKeepAlive( this ); + + if ( xVolRes.is() ) + { + xVolRes->removeResultListener( this ); + xVolRes = nullptr; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/address.cxx b/sc/source/core/tool/address.cxx new file mode 100644 index 0000000000..7b0c848a2b --- /dev/null +++ b/sc/source/core/tool/address.cxx @@ -0,0 +1,2531 @@ +/* -*- 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 <sal/config.h> + +#include <string_view> + +#include <address.hxx> +#include <global.hxx> +#include <compiler.hxx> +#include <document.hxx> +#include <docsh.hxx> +#include <externalrefmgr.hxx> + +#include <osl/diagnose.h> +#include <o3tl/underlyingenumvalue.hxx> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/sheet/ExternalLinkInfo.hpp> +#include <com/sun/star/sheet/ExternalLinkType.hpp> +#include <sfx2/objsh.hxx> +#include <tools/urlobj.hxx> +#include <sal/log.hxx> +#include <rtl/character.hxx> +#include <unotools/charclass.hxx> + +using namespace css; + +const ScAddress::Details ScAddress::detailsOOOa1( formula::FormulaGrammar::CONV_OOO, 0, 0 ); + +ScAddress::Details::Details ( const ScDocument& rDoc, + const ScAddress& rAddr ) : + eConv( rDoc.GetAddressConvention() ), + nRow( rAddr.Row() ), + nCol( rAddr.Col() ) +{} + +namespace { + +const sal_Unicode* parseQuotedNameWithBuffer( const sal_Unicode* pStart, const sal_Unicode* p, OUString& rName ) +{ + // The current character must be on the 2nd quote. + + // Push all the characters up to the current, but skip the very first + // character which is the opening quote. + OUStringBuffer aBuf(std::u16string_view(pStart+1, p-pStart-1)); + + ++p; // Skip the 2nd quote. + sal_Unicode cPrev = 0; + for (; *p; ++p) + { + if (*p == '\'') + { + if (cPrev == '\'') + { + // double single-quote equals one single quote. + aBuf.append(*p); + cPrev = 0; + continue; + } + } + else if (cPrev == '\'') + { + // We are past the closing quote. We're done! + rName = aBuf.makeStringAndClear(); + return p; + } + else + aBuf.append(*p); + cPrev = *p; + } + + return pStart; +} + +/** + * Parse from the opening single quote to the closing single quote. Inside + * the quotes, a single quote character is encoded by double single-quote + * characters. + * + * @param p pointer to the first character to begin parsing. + * @param rName (reference) parsed name within the quotes. If the name is + * empty, either the parsing failed or it's an empty quote. + * + * @return pointer to the character immediately after the closing single + * quote. + */ +const sal_Unicode* parseQuotedName( const sal_Unicode* p, OUString& rName ) +{ + if (*p != '\'') + return p; + + const sal_Unicode* pStart = p; + sal_Unicode cPrev = 0; + for (++p; *p; ++p) + { + if (*p == '\'') + { + if (cPrev == '\'') + { + // double single-quote equals one single quote. + return parseQuotedNameWithBuffer(pStart, p, rName); + } + } + else if (cPrev == '\'') + { + // We are past the closing quote. We're done! Skip the opening + // and closing quotes. + rName = OUString(pStart+1, p - pStart-2); + return p; + } + + cPrev = *p; + } + + rName.clear(); + return pStart; +} + +} + +static sal_Int64 sal_Unicode_strtol ( const sal_Unicode* p, const sal_Unicode** pEnd ) +{ + sal_Int64 accum = 0, prev = 0; + bool is_neg = false; + + if( *p == '-' ) + { + is_neg = true; + p++; + } + else if( *p == '+' ) + p++; + + while (rtl::isAsciiDigit( *p )) + { + accum = accum * 10 + *p - '0'; + if( accum < prev ) + { + *pEnd = nullptr; + return 0; + } + prev = accum; + p++; + } + + *pEnd = p; + return is_neg ? -accum : accum; +} + +static const sal_Unicode* lcl_eatWhiteSpace( const sal_Unicode* p ) +{ + if ( p ) + { + while( *p == ' ' ) + ++p; + } + return p; +} + +// Compare ignore case ASCII. +static bool lcl_isString( const sal_Unicode* p1, const OUString& rStr ) +{ + const size_t n = rStr.getLength(); + if (!n) + return false; + const sal_Unicode* p2 = rStr.getStr(); + for (size_t i=0; i<n; ++i) + { + if (!p1[i]) + return false; + if (p1[i] != p2[i]) + { + sal_Unicode c1 = p1[i]; + if ('A' <= c1 && c1 <= 'Z') + c1 += 0x20; + if (c1 < 'a' || 'z' < c1) + return false; // not a letter + + sal_Unicode c2 = p2[i]; + if ('A' <= c2 && c2 <= 'Z') + c2 += 0x20; + if (c2 < 'a' || 'z' < c2) + return false; // not a letter to match + + if (c1 != c2) + return false; // lower case doesn't match either + } + } + return true; +} + +/** Determines the number of sheets an external reference spans and sets + rRange.aEnd.nTab accordingly. If a sheet is not found, the corresponding + bits in rFlags are cleared. pExtInfo is filled if it wasn't already. If in + cached order rStartTabName comes after rEndTabName, pExtInfo->maTabName + is set to rEndTabName. + @returns <FALSE/> if pExtInfo is already filled and rExternDocName does not + result in the identical file ID. Else <TRUE/>. + */ +static bool lcl_ScRange_External_TabSpan( + ScRange & rRange, + ScRefFlags & rFlags, + ScAddress::ExternalInfo* pExtInfo, + const OUString & rExternDocName, + const OUString & rStartTabName, + const OUString & rEndTabName, + const ScDocument& rDoc ) +{ + if (rExternDocName.isEmpty()) + return !pExtInfo || !pExtInfo->mbExternal; + + ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); + if (pRefMgr->isOwnDocument( rExternDocName)) + { + // This is an internal document. Get the sheet positions from the + // ScDocument instance. + if (!rStartTabName.isEmpty()) + { + SCTAB nTab; + if (rDoc.GetTable(rStartTabName, nTab)) + rRange.aStart.SetTab(nTab); + } + + if (!rEndTabName.isEmpty()) + { + SCTAB nTab; + if (rDoc.GetTable(rEndTabName, nTab)) + rRange.aEnd.SetTab(nTab); + } + return !pExtInfo || !pExtInfo->mbExternal; + } + + sal_uInt16 nFileId = pRefMgr->getExternalFileId( rExternDocName); + + if (pExtInfo) + { + if (pExtInfo->mbExternal) + { + if (pExtInfo->mnFileId != nFileId) + return false; + } + else + { + pExtInfo->mbExternal = true; + pExtInfo->maTabName = rStartTabName; + pExtInfo->mnFileId = nFileId; + } + } + + if (rEndTabName.isEmpty() || rStartTabName == rEndTabName) + { + rRange.aEnd.SetTab( rRange.aStart.Tab()); + return true; + } + + SCTAB nSpan = pRefMgr->getCachedTabSpan( nFileId, rStartTabName, rEndTabName); + if (nSpan == -1) + rFlags &= ~ScRefFlags(ScRefFlags::TAB_VALID | ScRefFlags::TAB2_VALID); + else if (nSpan == 0) + rFlags &= ~ScRefFlags::TAB2_VALID; + else if (nSpan >= 1) + rRange.aEnd.SetTab( rRange.aStart.Tab() + nSpan - 1); + else // (nSpan < -1) + { + rRange.aEnd.SetTab( rRange.aStart.Tab() - nSpan - 1); + if (pExtInfo) + pExtInfo->maTabName = rEndTabName; + } + return true; +} + +/** Returns NULL if the string should be a sheet name, but is invalid. + Returns a pointer to the first character after the sheet name, if there was + any, else pointer to start. + @param pMsoxlQuoteStop + Starting _within_ a quoted name, but still may be 3D; quoted name stops + at pMsoxlQuoteStop + */ +static const sal_Unicode * lcl_XL_ParseSheetRef( const sal_Unicode* start, + OUString& rExternTabName, + bool bAllow3D, + const sal_Unicode* pMsoxlQuoteStop, + const OUString* pErrRef ) +{ + OUString aTabName; + const sal_Unicode *p = start; + + // XL only seems to use single quotes for sheet names. + if (pMsoxlQuoteStop) + { + const sal_Unicode* pCurrentStart = p; + while (p < pMsoxlQuoteStop) + { + if (*p == '\'') + { + // We pre-analyzed the quoting, no checks needed here. + if (*++p == '\'') + { + aTabName += std::u16string_view( pCurrentStart, + sal::static_int_cast<sal_Int32>( p - pCurrentStart)); + pCurrentStart = ++p; + } + } + else if (*p == ':') + { + break; // while + } + else + ++p; + } + if (pCurrentStart < p) + aTabName += std::u16string_view( pCurrentStart, sal::static_int_cast<sal_Int32>( p - pCurrentStart)); + if (aTabName.isEmpty()) + return nullptr; + if (p == pMsoxlQuoteStop) + ++p; // position on ! of ...'!... + if( *p != '!' && ( !bAllow3D || *p != ':' ) ) + return (!bAllow3D && *p == ':') ? p : start; + } + else if( *p == '\'') + { + p = parseQuotedName(p, aTabName); + if (aTabName.isEmpty()) + return nullptr; + } + else if (pErrRef && lcl_isString( p, *pErrRef) && p[pErrRef->getLength()] == '!') + { + p += pErrRef->getLength(); // position after "#REF!" on '!' + // XXX NOTE: caller has to check the name and that it wasn't quoted. + aTabName = *pErrRef; + } + else + { + bool only_digits = true; + + /* + * Valid: Normal!a1 + * Valid: x.y!a1 + * Invalid: .y!a1 + * + * Some names starting with digits are actually valid, but + * unparse quoted. Things are quite tricky: most sheet names + * starting with a digit are ok, but not those starting with + * "[0-9]*\." or "[0-9]+[eE]". + * + * Valid: 42!a1 + * Valid: 4x!a1 + * Invalid: 1.!a1 + * Invalid: 1e!a1 + */ + while( true ) + { + const sal_Unicode uc = *p; + if( rtl::isAsciiAlpha( uc ) || uc == '_' ) + { + if( only_digits && p != start && + (uc == 'e' || uc == 'E' ) ) + { + p = start; + break; + } + only_digits = false; + p++; + } + else if( rtl::isAsciiDigit( uc )) + { + p++; + } + else if( uc == '.' ) + { + if( only_digits ) // Valid, except after only digits. + { + p = start; + break; + } + p++; + } + else if (uc > 127) + { + // non ASCII character is allowed. + ++p; + } + else + break; + } + + if( *p != '!' && ( !bAllow3D || *p != ':' ) ) + return (!bAllow3D && *p == ':') ? p : start; + + aTabName += std::u16string_view( start, sal::static_int_cast<sal_Int32>( p - start ) ); + } + + rExternTabName = aTabName; + return p; +} + +/** Tries to obtain the external document index and replace by actual document + name. + + @param ppErrRet + Contains the default pointer the caller would return if this method + returns FALSE, may be replaced by NULL for type or data errors. + + @returns FALSE only if the input name is numeric and not within the index + sequence, or the link type cannot be determined or data mismatch. Returns + TRUE in all other cases, also when there is no index sequence or the input + name is not numeric. + */ +static bool lcl_XL_getExternalDoc( const sal_Unicode** ppErrRet, OUString& rExternDocName, + const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks ) +{ + // 1-based, sequence starts with an empty element. + if (pExternalLinks && pExternalLinks->hasElements()) + { + // A numeric "document name" is an index into the sequence. + if (CharClass::isAsciiNumeric( rExternDocName)) + { + sal_Int32 i = rExternDocName.toInt32(); + if (i < 0 || i >= pExternalLinks->getLength()) + return false; // with default *ppErrRet + const sheet::ExternalLinkInfo & rInfo = (*pExternalLinks)[i]; + switch (rInfo.Type) + { + case sheet::ExternalLinkType::DOCUMENT : + { + OUString aStr; + if (!(rInfo.Data >>= aStr)) + { + SAL_INFO( + "sc.core", + "Data type mismatch for ExternalLinkInfo " + << i); + *ppErrRet = nullptr; + return false; + } + rExternDocName = aStr; + } + break; + case sheet::ExternalLinkType::SELF : + return false; // ??? + case sheet::ExternalLinkType::SPECIAL : + // silently return nothing (do not assert), caller has to handle this + *ppErrRet = nullptr; + return false; + default: + SAL_INFO( + "sc.core", + "unhandled ExternalLinkType " << rInfo.Type + << " for index " << i); + *ppErrRet = nullptr; + return false; + } + } + } + return true; +} + +const sal_Unicode* ScRange::Parse_XL_Header( + const sal_Unicode* p, + const ScDocument& rDoc, + OUString& rExternDocName, + OUString& rStartTabName, + OUString& rEndTabName, + ScRefFlags& nFlags, + bool bOnlyAcceptSingle, + const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks, + const OUString* pErrRef ) +{ + const sal_Unicode* startTabs, *start = p; + ScRefFlags nSaveFlags = nFlags; + + // Is this an external reference ? + rStartTabName.clear(); + rEndTabName.clear(); + rExternDocName.clear(); + const sal_Unicode* pMsoxlQuoteStop = nullptr; + if (*p == '[') + { + ++p; + // Only single quotes are correct, and a double single quote escapes a + // single quote text inside the quoted text. + if (*p == '\'') + { + p = parseQuotedName(p, rExternDocName); + if (*p != ']' || rExternDocName.isEmpty()) + { + rExternDocName.clear(); + return start; + } + } + else + { + // non-quoted file name. + p = ScGlobal::UnicodeStrChr( start+1, ']' ); + if( p == nullptr ) + return start; + rExternDocName += std::u16string_view( start+1, sal::static_int_cast<sal_Int32>( p-(start+1) ) ); + } + ++p; + + const sal_Unicode* pErrRet = start; + if (!lcl_XL_getExternalDoc( &pErrRet, rExternDocName, pExternalLinks)) + return pErrRet; + + rExternDocName = ScGlobal::GetAbsDocName(rExternDocName, rDoc.GetDocumentShell()); + } + else if (*p == '\'') + { + // Sickness in Excel's ODF msoxl namespace: + // 'E:\[EXTDATA8.XLS]Sheet1'!$A$7 or + // 'E:\[EXTDATA12B.XLSB]Sheet1:Sheet3'!$A$11 + // But, 'Sheet1'!B3 would also be a valid! + // Excel does not allow [ and ] characters in sheet names though. + // But, more sickness comes with MOOXML as there may be + // '[1]Sheet 4'!$A$1 where [1] is the external doc's index. + p = parseQuotedName(p, rExternDocName); + if (*p != '!') + { + rExternDocName.clear(); + return start; + } + if (!rExternDocName.isEmpty()) + { + sal_Int32 nOpen = rExternDocName.indexOf( '['); + if (nOpen == -1) + rExternDocName.clear(); + else + { + sal_Int32 nClose = rExternDocName.indexOf( ']', nOpen+1); + if (nClose == -1) + rExternDocName.clear(); + else + { + rExternDocName = rExternDocName.copy(0, nClose); + rExternDocName = rExternDocName.replaceAt( nOpen, 1, u""); + pMsoxlQuoteStop = p - 1; // the ' quote char + // There may be embedded escaped quotes, just matching the + // doc name's length may not work. + for (p = start; *p != '['; ++p) + ; + for ( ; *p != ']'; ++p) + ; + ++p; + + // Handle '[1]Sheet 4'!$A$1 + if (nOpen == 0) + { + const sal_Unicode* pErrRet = start; + if (!lcl_XL_getExternalDoc( &pErrRet, rExternDocName, pExternalLinks)) + return pErrRet; + } + } + } + } + if (rExternDocName.isEmpty()) + p = start; + } + + startTabs = p; + p = lcl_XL_ParseSheetRef( p, rStartTabName, !bOnlyAcceptSingle, pMsoxlQuoteStop, pErrRef); + if( nullptr == p ) + return start; // invalid tab + if (bOnlyAcceptSingle && *p == ':') + return nullptr; // 3D + const sal_Unicode* startEndTabs = nullptr; + if( p != startTabs ) + { + nFlags |= ScRefFlags::TAB_VALID | ScRefFlags::TAB_3D | ScRefFlags::TAB_ABS; + if( *p == ':' ) // 3d ref + { + startEndTabs = p + 1; + p = lcl_XL_ParseSheetRef( startEndTabs, rEndTabName, false, pMsoxlQuoteStop, pErrRef); + if( p == nullptr ) + { + nFlags = nSaveFlags; + return start; // invalid tab + } + nFlags |= ScRefFlags::TAB2_VALID | ScRefFlags::TAB2_3D | ScRefFlags::TAB2_ABS; + } + else + { + // If only one sheet is given, the full reference is still valid, + // only the second 3D flag is not set. + nFlags |= ScRefFlags::TAB2_VALID | ScRefFlags::TAB2_ABS; + aEnd.SetTab( aStart.Tab() ); + } + + if( *p++ != '!' ) + { + nFlags = nSaveFlags; + return start; // syntax error + } + else + p = lcl_eatWhiteSpace( p ); + } + else + { + nFlags |= ScRefFlags::TAB_VALID | ScRefFlags::TAB2_VALID; + // Use the current tab, it needs to be passed in. : aEnd.SetTab( .. ); + } + + if (!rExternDocName.isEmpty()) + { + ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); + pRefMgr->convertToAbsName(rExternDocName); + } + else + { + // Internal reference. + if (rStartTabName.isEmpty()) + { + nFlags = nSaveFlags; + return start; + } + + SCTAB nTab; + if ((pErrRef && *startTabs != '\'' && rStartTabName == *pErrRef) || !rDoc.GetTable(rStartTabName, nTab)) + { + // invalid table name. + nFlags &= ~ScRefFlags::TAB_VALID; + nTab = -1; + } + + aStart.SetTab(nTab); + aEnd.SetTab(nTab); + + if (!rEndTabName.isEmpty()) + { + if ((pErrRef && startEndTabs && *startEndTabs != '\'' && rEndTabName == *pErrRef) || + !rDoc.GetTable(rEndTabName, nTab)) + { + // invalid table name. + nFlags &= ~ScRefFlags::TAB2_VALID; + nTab = -1; + } + + aEnd.SetTab(nTab); + } + } + return p; +} + +static const sal_Unicode* lcl_r1c1_get_col( const ScSheetLimits& rSheetLimits, + const sal_Unicode* p, + const ScAddress::Details& rDetails, + ScAddress* pAddr, ScRefFlags* nFlags ) +{ + const sal_Unicode *pEnd; + sal_Int64 n; + bool isRelative; + + if( p[0] == '\0' ) + return nullptr; + + p++; + isRelative = *p == '['; + if( isRelative ) + p++; + n = sal_Unicode_strtol( p, &pEnd ); + if( nullptr == pEnd ) + return nullptr; + + if( p == pEnd ) // C is a relative ref with offset 0 + { + if( isRelative ) + return nullptr; + n = rDetails.nCol; + } + else if( isRelative ) + { + if( *pEnd != ']' ) + return nullptr; + n += rDetails.nCol; + pEnd++; + } + else + { + *nFlags |= ScRefFlags::COL_ABS; + n--; + } + + if( n < 0 || n >= rSheetLimits.GetMaxColCount()) + return nullptr; + pAddr->SetCol( static_cast<SCCOL>( n ) ); + *nFlags |= ScRefFlags::COL_VALID; + + return pEnd; +} + +static const sal_Unicode* lcl_r1c1_get_row( + const ScSheetLimits& rSheetLimits, + const sal_Unicode* p, + const ScAddress::Details& rDetails, + ScAddress* pAddr, ScRefFlags* nFlags ) +{ + const sal_Unicode *pEnd; + bool isRelative; + + if( p[0] == '\0' ) + return nullptr; + + p++; + isRelative = *p == '['; + if( isRelative ) + p++; + sal_Int64 n = sal_Unicode_strtol( p, &pEnd ); + if( nullptr == pEnd ) + return nullptr; + + if( p == pEnd ) // R is a relative ref with offset 0 + { + if( isRelative ) + return nullptr; + n = rDetails.nRow; + } + else if( isRelative ) + { + if( *pEnd != ']' ) + return nullptr; + n += rDetails.nRow; + pEnd++; + } + else + { + *nFlags |= ScRefFlags::ROW_ABS; + n--; + } + + if( n < 0 || n >= rSheetLimits.GetMaxRowCount() ) + return nullptr; + pAddr->SetRow( static_cast<SCROW>( n ) ); + *nFlags |= ScRefFlags::ROW_VALID; + + return pEnd; +} + +static ScRefFlags lcl_ScRange_Parse_XL_R1C1( ScRange& r, + const sal_Unicode* p, + const ScDocument& rDoc, + const ScAddress::Details& rDetails, + bool bOnlyAcceptSingle, + ScAddress::ExternalInfo* pExtInfo, + sal_Int32* pSheetEndPos ) +{ + const sal_Unicode* const pStart = p; + if (pSheetEndPos) + *pSheetEndPos = 0; + const sal_Unicode* pTmp = nullptr; + OUString aExternDocName, aStartTabName, aEndTabName; + ScRefFlags nFlags = ScRefFlags::VALID | ScRefFlags::TAB_VALID; + // Keep in mind that nFlags2 gets left-shifted by 4 bits before being merged. + ScRefFlags nFlags2 = ScRefFlags::TAB_VALID; + + p = r.Parse_XL_Header( p, rDoc, aExternDocName, aStartTabName, + aEndTabName, nFlags, bOnlyAcceptSingle ); + + ScRefFlags nBailOutFlags = ScRefFlags::ZERO; + if (pSheetEndPos && pStart < p && (nFlags & ScRefFlags::TAB_VALID) && (nFlags & ScRefFlags::TAB_3D)) + { + *pSheetEndPos = p - pStart; + nBailOutFlags = ScRefFlags::TAB_VALID | ScRefFlags::TAB_3D; + } + + if (!aExternDocName.isEmpty()) + lcl_ScRange_External_TabSpan( r, nFlags, pExtInfo, aExternDocName, + aStartTabName, aEndTabName, rDoc); + + if( nullptr == p ) + return ScRefFlags::ZERO; + + if( *p == 'R' || *p == 'r' ) + { + if( nullptr == (p = lcl_r1c1_get_row( rDoc.GetSheetLimits(), p, rDetails, &r.aStart, &nFlags )) ) + return nBailOutFlags; + + if( *p != 'C' && *p != 'c' ) // full row R# + { + if( p[0] != ':' || (p[1] != 'R' && p[1] != 'r' ) || + nullptr == (pTmp = lcl_r1c1_get_row( rDoc.GetSheetLimits(), p+1, rDetails, &r.aEnd, &nFlags2 ))) + { + // Only the initial row number is given, or the second row + // number is invalid. Fallback to just the initial R + applyStartToEndFlags(nFlags); + r.aEnd.SetRow( r.aStart.Row() ); + } + else // pTmp != nullptr + { + // Full row range successfully parsed. + applyStartToEndFlags(nFlags, nFlags2); + p = pTmp; + } + + if (p[0] != 0) + { + // any trailing invalid character must invalidate the whole address. + nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID | + ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID); + return nFlags; + } + + nFlags |= + ScRefFlags::COL_VALID | ScRefFlags::COL2_VALID | + ScRefFlags::COL_ABS | ScRefFlags::COL2_ABS; + r.aStart.SetCol( 0 ); + r.aEnd.SetCol( rDoc.MaxCol() ); + + return bOnlyAcceptSingle ? ScRefFlags::ZERO : nFlags; + } + else if( nullptr == (p = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p, rDetails, &r.aStart, &nFlags ))) + { + return ScRefFlags::ZERO; + } + + if( p[0] != ':' || + (p[1] != 'R' && p[1] != 'r') || + nullptr == (pTmp = lcl_r1c1_get_row( rDoc.GetSheetLimits(), p+1, rDetails, &r.aEnd, &nFlags2 )) || + (*pTmp != 'C' && *pTmp != 'c') || + nullptr == (pTmp = lcl_r1c1_get_col( rDoc.GetSheetLimits(), pTmp, rDetails, &r.aEnd, &nFlags2 ))) + { + // single cell reference + + if (p[0] != 0) + { + // any trailing invalid character must invalidate the whole address. + nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID); + return nFlags; + } + + return bOnlyAcceptSingle ? nFlags : ScRefFlags::ZERO; + } + assert(pTmp); + p = pTmp; + + // double reference + + if (p[0] != 0) + { + // any trailing invalid character must invalidate the whole range. + nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID | + ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID); + return nFlags; + } + + applyStartToEndFlags(nFlags, nFlags2); + return bOnlyAcceptSingle ? ScRefFlags::ZERO : nFlags; + } + else if( *p == 'C' || *p == 'c' ) // full col C# + { + if( nullptr == (p = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p, rDetails, &r.aStart, &nFlags ))) + return nBailOutFlags; + + if( p[0] != ':' || (p[1] != 'C' && p[1] != 'c') || + nullptr == (pTmp = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p+1, rDetails, &r.aEnd, &nFlags2 ))) + { // Fallback to just the initial C + applyStartToEndFlags(nFlags); + r.aEnd.SetCol( r.aStart.Col() ); + } + else // pTmp != nullptr + { + applyStartToEndFlags(nFlags, nFlags2); + p = pTmp; + } + + if (p[0] != 0) + { + // any trailing invalid character must invalidate the whole address. + nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID | + ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID); + return nFlags; + } + + nFlags |= + ScRefFlags::ROW_VALID | ScRefFlags::ROW2_VALID | + ScRefFlags::ROW_ABS | ScRefFlags::ROW2_ABS; + r.aStart.SetRow( 0 ); + r.aEnd.SetRow( rDoc.MaxRow() ); + + return bOnlyAcceptSingle ? ScRefFlags::ZERO : nFlags; + } + + return nBailOutFlags; +} + +static const sal_Unicode* lcl_a1_get_col( const ScDocument& rDoc, + const sal_Unicode* p, + ScAddress* pAddr, + ScRefFlags* nFlags, + const OUString* pErrRef ) +{ + if( *p == '$' ) + { + *nFlags |= ScRefFlags::COL_ABS; + p++; + } + + if (pErrRef && lcl_isString( p, *pErrRef)) + { + p += pErrRef->getLength(); + *nFlags &= ~ScRefFlags::COL_VALID; + pAddr->SetCol(-1); + return p; + } + + if( !rtl::isAsciiAlpha( *p ) ) + return nullptr; + + sal_Int64 nCol = rtl::toAsciiUpperCase( *p++ ) - 'A'; + const SCCOL nMaxCol = rDoc.MaxCol(); + while (nCol <= nMaxCol && rtl::isAsciiAlpha(*p)) + nCol = ((nCol + 1) * 26) + rtl::toAsciiUpperCase( *p++ ) - 'A'; + if( nCol > nMaxCol || nCol < 0 || rtl::isAsciiAlpha( *p ) ) + return nullptr; + + *nFlags |= ScRefFlags::COL_VALID; + pAddr->SetCol( sal::static_int_cast<SCCOL>( nCol )); + + return p; +} + +static const sal_Unicode* lcl_a1_get_row( const ScDocument& rDoc, + const sal_Unicode* p, + ScAddress* pAddr, + ScRefFlags* nFlags, + const OUString* pErrRef ) +{ + const sal_Unicode *pEnd; + + if( *p == '$' ) + { + *nFlags |= ScRefFlags::ROW_ABS; + p++; + } + + if (pErrRef && lcl_isString( p, *pErrRef)) + { + p += pErrRef->getLength(); + *nFlags &= ~ScRefFlags::ROW_VALID; + pAddr->SetRow(-1); + return p; + } + + sal_Int64 n = sal_Unicode_strtol( p, &pEnd ) - 1; + if( nullptr == pEnd || p == pEnd || n < 0 || n > rDoc.MaxRow() ) + return nullptr; + + *nFlags |= ScRefFlags::ROW_VALID; + pAddr->SetRow( sal::static_int_cast<SCROW>(n) ); + + return pEnd; +} + +/// B:B or 2:2, but not B:2 or 2:B or B2:B or B:B2 or ... +static bool isValidSingleton( ScRefFlags nFlags, ScRefFlags nFlags2 ) +{ + bool bCols = (nFlags & ScRefFlags::COL_VALID) && ((nFlags & ScRefFlags::COL2_VALID) || (nFlags2 & ScRefFlags::COL_VALID)); + bool bRows = (nFlags & ScRefFlags::ROW_VALID) && ((nFlags & ScRefFlags::ROW2_VALID) || (nFlags2 & ScRefFlags::ROW_VALID)); + return bCols != bRows; +} + +static ScRefFlags lcl_ScRange_Parse_XL_A1( ScRange& r, + const sal_Unicode* p, + const ScDocument& rDoc, + bool bOnlyAcceptSingle, + ScAddress::ExternalInfo* pExtInfo, + const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks, + sal_Int32* pSheetEndPos, + const OUString* pErrRef ) +{ + const sal_Unicode* const pStart = p; + if (pSheetEndPos) + *pSheetEndPos = 0; + const sal_Unicode* tmp1, *tmp2; + OUString aExternDocName, aStartTabName, aEndTabName; // for external link table + ScRefFlags nFlags = ScRefFlags::VALID | ScRefFlags::TAB_VALID, nFlags2 = ScRefFlags::TAB_VALID; + + p = r.Parse_XL_Header( p, rDoc, aExternDocName, aStartTabName, + aEndTabName, nFlags, bOnlyAcceptSingle, pExternalLinks, pErrRef ); + + ScRefFlags nBailOutFlags = ScRefFlags::ZERO; + if (pSheetEndPos && pStart < p && (nFlags & ScRefFlags::TAB_VALID) && (nFlags & ScRefFlags::TAB_3D)) + { + *pSheetEndPos = p - pStart; + nBailOutFlags = ScRefFlags::TAB_VALID | ScRefFlags::TAB_3D; + } + + if (!aExternDocName.isEmpty()) + lcl_ScRange_External_TabSpan( r, nFlags, pExtInfo, aExternDocName, + aStartTabName, aEndTabName, rDoc); + + if( nullptr == p ) + return nBailOutFlags; + + tmp1 = lcl_a1_get_col( rDoc, p, &r.aStart, &nFlags, pErrRef); + if( tmp1 == nullptr ) // Is it a row only reference 3:5 + { + if( bOnlyAcceptSingle ) // by definition full row refs are ranges + return nBailOutFlags; + + tmp1 = lcl_a1_get_row( rDoc, p, &r.aStart, &nFlags, pErrRef); + + tmp1 = lcl_eatWhiteSpace( tmp1 ); + if( !tmp1 || *tmp1++ != ':' ) // Even a singleton requires ':' (eg 2:2) + return nBailOutFlags; + + tmp1 = lcl_eatWhiteSpace( tmp1 ); + tmp2 = lcl_a1_get_row( rDoc, tmp1, &r.aEnd, &nFlags2, pErrRef); + if( !tmp2 || *tmp2 != 0 ) // Must have fully parsed a singleton. + return nBailOutFlags; + + r.aStart.SetCol( 0 ); r.aEnd.SetCol( rDoc.MaxCol() ); + nFlags |= + ScRefFlags::COL_VALID | ScRefFlags::COL2_VALID | + ScRefFlags::COL_ABS | ScRefFlags::COL2_ABS; + applyStartToEndFlags(nFlags, nFlags2); + return nFlags; + } + + tmp2 = lcl_a1_get_row( rDoc, tmp1, &r.aStart, &nFlags, pErrRef); + if( tmp2 == nullptr ) // check for col only reference F:H + { + if( bOnlyAcceptSingle ) // by definition full col refs are ranges + return nBailOutFlags; + + tmp1 = lcl_eatWhiteSpace( tmp1 ); + if( *tmp1++ != ':' ) // Even a singleton requires ':' (eg F:F) + return nBailOutFlags; + + tmp1 = lcl_eatWhiteSpace( tmp1 ); + tmp2 = lcl_a1_get_col( rDoc, tmp1, &r.aEnd, &nFlags2, pErrRef); + if( !tmp2 || *tmp2 != 0 ) // Must have fully parsed a singleton. + return nBailOutFlags; + + r.aStart.SetRow( 0 ); r.aEnd.SetRow( rDoc.MaxRow() ); + nFlags |= + ScRefFlags::ROW_VALID | ScRefFlags::ROW2_VALID | + ScRefFlags::ROW_ABS | ScRefFlags::ROW2_ABS; + applyStartToEndFlags(nFlags, nFlags2); + return nFlags; + } + + // prepare as if it's a singleton, in case we want to fall back */ + r.aEnd.SetCol( r.aStart.Col() ); + r.aEnd.SetRow( r.aStart.Row() ); // don't overwrite sheet number as parsed in Parse_XL_Header() + + if ( bOnlyAcceptSingle ) + { + if ( *tmp2 == 0 ) + return nFlags; + else + { + // any trailing invalid character must invalidate the address. + nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID); + return nFlags; + } + } + + tmp2 = lcl_eatWhiteSpace( tmp2 ); + if( *tmp2 != ':' ) + { + // Sheet1:Sheet2!C4 is a valid range, without a second sheet it is + // not. Any trailing invalid character invalidates the range. + if (*tmp2 == 0 && (nFlags & ScRefFlags::TAB2_3D)) + { + if (nFlags & ScRefFlags::COL_ABS) + nFlags |= ScRefFlags::COL2_ABS; + if (nFlags & ScRefFlags::ROW_ABS) + nFlags |= ScRefFlags::ROW2_ABS; + } + else + nFlags &= ~ScRefFlags(ScRefFlags::VALID | + ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID | + ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID); + return nFlags; + } + + p = lcl_eatWhiteSpace( tmp2+1 ); // after ':' + tmp1 = lcl_a1_get_col( rDoc, p, &r.aEnd, &nFlags2, pErrRef); + if( !tmp1 && aEndTabName.isEmpty() ) // Probably the aEndTabName was specified after the first range + { + p = lcl_XL_ParseSheetRef( p, aEndTabName, false, nullptr, pErrRef); + if( p ) + { + SCTAB nTab = 0; + if( !aEndTabName.isEmpty() && rDoc.GetTable( aEndTabName, nTab ) ) + { + r.aEnd.SetTab( nTab ); + nFlags |= ScRefFlags::TAB2_VALID | ScRefFlags::TAB2_3D | ScRefFlags::TAB2_ABS; + } + if (*p == '!' || *p == ':') + p = lcl_eatWhiteSpace( p+1 ); + tmp1 = lcl_a1_get_col( rDoc, p, &r.aEnd, &nFlags2, pErrRef); + } + } + if( !tmp1 ) // strange, but maybe valid singleton + return isValidSingleton( nFlags, nFlags2) ? nFlags : (nFlags & ~ScRefFlags::VALID); + + tmp2 = lcl_a1_get_row( rDoc, tmp1, &r.aEnd, &nFlags2, pErrRef); + if( !tmp2 ) // strange, but maybe valid singleton + return isValidSingleton( nFlags, nFlags2) ? nFlags : (nFlags & ~ScRefFlags::VALID); + + if ( *tmp2 != 0 ) + { + // any trailing invalid character must invalidate the range. + nFlags &= ~ScRefFlags(ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID | ScRefFlags::TAB_VALID | + ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID); + return nFlags; + } + + applyStartToEndFlags(nFlags, nFlags2); + return nFlags; +} + +/** + @param p pointer to null-terminated sal_Unicode string + @param rRawRes returns ScRefFlags::... flags without the final check for full + validity that is applied to the return value, with which + two addresses that form a column or row singleton range, + e.g. A:A or 1:1, can be detected. Used in + lcl_ScRange_Parse_OOo(). + @param pRange pointer to range where rAddr effectively is *pRange->aEnd, + used in conjunction with pExtInfo to determine the tab span + of a 3D reference. + */ +static ScRefFlags lcl_ScAddress_Parse_OOo( const sal_Unicode* p, const ScDocument& rDoc, ScAddress& rAddr, + ScRefFlags& rRawRes, + ScAddress::ExternalInfo* pExtInfo, + ScRange* pRange, + sal_Int32* pSheetEndPos, + const OUString* pErrRef ) +{ + const sal_Unicode* const pStart = p; + if (pSheetEndPos) + *pSheetEndPos = 0; + ScRefFlags nRes = ScRefFlags::ZERO; + rRawRes = ScRefFlags::ZERO; + OUString aDocName; // the pure Document Name + OUString aTab; + bool bExtDoc = false; + bool bExtDocInherited = false; + + // Lets see if this is a reference to something in an external file. A + // document name is always quoted and has a trailing #. + if (*p == '\'') + { + OUString aTmp; + p = parseQuotedName(p, aTmp); + aDocName = aTmp; + if (*p++ == SC_COMPILER_FILE_TAB_SEP) + bExtDoc = true; + else + // This is not a document name. Perhaps a quoted relative table + // name. + p = pStart; + } + else if (pExtInfo && pExtInfo->mbExternal) + { + // This is an external reference. + bExtDoc = bExtDocInherited = true; + } + + SCCOL nCol = 0; + SCROW nRow = 0; + SCTAB nTab = 0; + ScRefFlags nBailOutFlags = ScRefFlags::ZERO; + ScRefFlags nBits = ScRefFlags::TAB_VALID; + const sal_Unicode* q; + if ( ScGlobal::FindUnquoted( p, '.') ) + { + nRes |= ScRefFlags::TAB_3D; + if ( bExtDoc ) + nRes |= ScRefFlags::TAB_ABS; + if (*p == '$') + { + nRes |= ScRefFlags::TAB_ABS; + p++; + } + + if (pErrRef && lcl_isString( p, *pErrRef) && p[pErrRef->getLength()] == '.') + { + // #REF! particle of an invalidated reference plus sheet separator. + p += pErrRef->getLength() + 1; + nRes &= ~ScRefFlags::TAB_VALID; + nTab = -1; + } + else + { + if (*p == '\'') + { + // Tokens that start at ' can have anything in them until a final + // ' but '' marks an escaped '. We've earlier guaranteed that a + // string containing '' will be surrounded by '. + p = parseQuotedName(p, aTab); + } + else + { + OUStringBuffer aTabAcc; + while (*p) + { + if( *p == '.') + break; + + if( *p == '\'' ) + { + p++; break; + } + aTabAcc.append(*p); + p++; + } + aTab = aTabAcc.makeStringAndClear(); + } + if (*p != '.') + nBits = ScRefFlags::ZERO; + else + { + ++p; + if (!bExtDoc && !rDoc.GetTable( aTab, nTab )) + nBits = ScRefFlags::ZERO; + } + } + + if (pSheetEndPos && (nBits & ScRefFlags::TAB_VALID)) + { + *pSheetEndPos = p - pStart; + nBailOutFlags = ScRefFlags::TAB_VALID | ScRefFlags::TAB_3D; + } + } + else + { + if (bExtDoc && !bExtDocInherited) + return nRes; // After a document a sheet must follow. + nTab = rAddr.Tab(); + } + nRes |= nBits; + + q = p; + if (*p) + { + nBits = ScRefFlags::COL_VALID; + if (*p == '$') + { + nBits |= ScRefFlags::COL_ABS; + p++; + } + + if (pErrRef && lcl_isString( p, *pErrRef)) + { + // #REF! particle of an invalidated reference. + p += pErrRef->getLength(); + nBits &= ~ScRefFlags::COL_VALID; + nCol = -1; + } + else + { + if (rtl::isAsciiAlpha( *p )) + { + const SCCOL nMaxCol = rDoc.MaxCol(); + sal_Int64 n = rtl::toAsciiUpperCase( *p++ ) - 'A'; + while (n < nMaxCol && rtl::isAsciiAlpha(*p)) + n = ((n + 1) * 26) + rtl::toAsciiUpperCase( *p++ ) - 'A'; + if (n > nMaxCol || n < 0 || (*p && *p != '$' && !rtl::isAsciiDigit( *p ) && + (!pErrRef || !lcl_isString( p, *pErrRef)))) + nBits = ScRefFlags::ZERO; + else + nCol = sal::static_int_cast<SCCOL>( n ); + } + else + nBits = ScRefFlags::ZERO; + + if( nBits == ScRefFlags::ZERO ) + p = q; + } + nRes |= nBits; + } + + q = p; + if (*p) + { + nBits = ScRefFlags::ROW_VALID; + if (*p == '$') + { + nBits |= ScRefFlags::ROW_ABS; + p++; + } + + if (pErrRef && lcl_isString( p, *pErrRef)) + { + // #REF! particle of an invalidated reference. + p += pErrRef->getLength(); + // Clearing the ROW_VALID bit here is not possible because of the + // check at the end whether only a valid column was detected in + // which case all bits are cleared because it could be any other + // name. Instead, set to an absolute invalid row value. This will + // display a $#REF! instead of #REF! if the error value was + // relative, but live with it. + nBits |= ScRefFlags::ROW_ABS; + nRow = -1; + } + else + { + if( !rtl::isAsciiDigit( *p ) ) + { + nBits = ScRefFlags::ZERO; + nRow = -1; + } + else + { + sal_Int64 n = rtl_ustr_toInt32( p, 10 ) - 1; + while (rtl::isAsciiDigit( *p )) + p++; + const SCROW nMaxRow = rDoc.MaxRow(); + if( n < 0 || n > nMaxRow ) + nBits = ScRefFlags::ZERO; + nRow = sal::static_int_cast<SCROW>(n); + } + if( nBits == ScRefFlags::ZERO ) + p = q; + } + nRes |= nBits; + } + + rAddr.Set( nCol, nRow, nTab ); + + if (!*p && bExtDoc) + { + ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); + + // Need document name if inherited. + if (bExtDocInherited) + { + // The FileId was created using the original file name, so + // obtain that. Otherwise lcl_ScRange_External_TabSpan() would + // retrieve a FileId for the real name and bail out if that + // differed from pExtInfo->mnFileId, as is the case when + // loading documents that refer external files relative to the + // current own document but were saved from a different path + // than loaded. + const OUString* pFileName = pRefMgr->getExternalFileName( pExtInfo->mnFileId, true); + if (pFileName) + aDocName = *pFileName; + else + nRes = ScRefFlags::ZERO; + } + pRefMgr->convertToAbsName(aDocName); + + if ((!pExtInfo || !pExtInfo->mbExternal) && pRefMgr->isOwnDocument(aDocName)) + { + if (!rDoc.GetTable( aTab, nTab )) + nRes = ScRefFlags::ZERO; + else + { + rAddr.SetTab( nTab); + nRes |= ScRefFlags::TAB_VALID; + } + } + else + { + if (!pExtInfo) + nRes = ScRefFlags::ZERO; + else + { + if (!pExtInfo->mbExternal) + { + sal_uInt16 nFileId = pRefMgr->getExternalFileId(aDocName); + + pExtInfo->mbExternal = true; + pExtInfo->maTabName = aTab; + pExtInfo->mnFileId = nFileId; + + if (pRefMgr->getSingleRefToken(nFileId, aTab, + ScAddress(nCol, nRow, 0), nullptr, + &nTab)) + { + rAddr.SetTab( nTab); + nRes |= ScRefFlags::TAB_VALID; + } + else + nRes = ScRefFlags::ZERO; + } + else + { + // This is a call for the second part of the reference, + // we must have the range to adapt tab span. + if (!pRange) + nRes = ScRefFlags::ZERO; + else + { + ScRefFlags nFlags = nRes | ScRefFlags::TAB2_VALID; + if (!lcl_ScRange_External_TabSpan( *pRange, nFlags, + pExtInfo, aDocName, + pExtInfo->maTabName, aTab, rDoc)) + nRes &= ~ScRefFlags::TAB_VALID; + else + { + if (nFlags & ScRefFlags::TAB2_VALID) + { + rAddr.SetTab( pRange->aEnd.Tab()); + nRes |= ScRefFlags::TAB_VALID; + } + else + nRes &= ~ScRefFlags::TAB_VALID; + } + } + } + } + } + } + else if (bExtDoc && pExtInfo && !bExtDocInherited && !pExtInfo->mbExternal && pSheetEndPos) + { + // Pass partial info up to caller, there may be an external name + // following, and if after *pSheetEndPos it's external sheet-local. + // For global names aTab is empty and *pSheetEndPos==0. + pExtInfo->mbExternal = true; + pExtInfo->maTabName = aTab; + pExtInfo->mnFileId = rDoc.GetExternalRefManager()->getExternalFileId(aDocName); + } + + rRawRes |= nRes; + + if ( !(nRes & ScRefFlags::ROW_VALID) && (nRes & ScRefFlags::COL_VALID) + && !( (nRes & ScRefFlags::TAB_3D) && (nRes & ScRefFlags::TAB_VALID)) ) + { // no Row, no Tab, but Col => DM (...), B (...) et al + nRes = ScRefFlags::ZERO; + } + if( !*p ) + { + ScRefFlags nMask = nRes & ( ScRefFlags::ROW_VALID | ScRefFlags::COL_VALID | ScRefFlags::TAB_VALID ); + if( nMask == ( ScRefFlags::ROW_VALID | ScRefFlags::COL_VALID | ScRefFlags::TAB_VALID ) ) + nRes |= ScRefFlags::VALID; + } + else + nRes = rRawRes = nBailOutFlags; + return nRes; +} + +static ScRefFlags lcl_ScAddress_Parse ( const sal_Unicode* p, const ScDocument& rDoc, ScAddress& rAddr, + const ScAddress::Details& rDetails, + ScAddress::ExternalInfo* pExtInfo, + const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks, + sal_Int32* pSheetEndPos, + const OUString* pErrRef ) +{ + if( !*p ) + return ScRefFlags::ZERO; + + switch (rDetails.eConv) + { + case formula::FormulaGrammar::CONV_XL_A1: + case formula::FormulaGrammar::CONV_XL_OOX: + { + ScRange rRange = rAddr; + ScRefFlags nFlags = lcl_ScRange_Parse_XL_A1( + rRange, p, rDoc, true, pExtInfo, + (rDetails.eConv == formula::FormulaGrammar::CONV_XL_OOX ? pExternalLinks : nullptr), + pSheetEndPos, pErrRef); + rAddr = rRange.aStart; + return nFlags; + } + case formula::FormulaGrammar::CONV_XL_R1C1: + { + ScRange rRange = rAddr; + ScRefFlags nFlags = lcl_ScRange_Parse_XL_R1C1( rRange, p, rDoc, rDetails, true, pExtInfo, pSheetEndPos); + rAddr = rRange.aStart; + return nFlags; + } + default : + case formula::FormulaGrammar::CONV_OOO: + { + ScRefFlags nRawRes = ScRefFlags::ZERO; + return lcl_ScAddress_Parse_OOo( p, rDoc, rAddr, nRawRes, pExtInfo, nullptr, pSheetEndPos, pErrRef); + } + } +} + +bool ConvertSingleRef( const ScDocument& rDoc, const OUString& rRefString, + SCTAB nDefTab, ScRefAddress& rRefAddress, + const ScAddress::Details& rDetails, + ScAddress::ExternalInfo* pExtInfo /* = NULL */ ) +{ + bool bRet = false; + if (pExtInfo || (ScGlobal::FindUnquoted( rRefString, SC_COMPILER_FILE_TAB_SEP) == -1)) + { + ScAddress aAddr( 0, 0, nDefTab ); + ScRefFlags nRes = aAddr.Parse( rRefString, rDoc, rDetails, pExtInfo); + if ( nRes & ScRefFlags::VALID ) + { + rRefAddress.Set( aAddr, + ((nRes & ScRefFlags::COL_ABS) == ScRefFlags::ZERO), + ((nRes & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO), + ((nRes & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO)); + bRet = true; + } + } + return bRet; +} + +bool ConvertDoubleRef( const ScDocument& rDoc, const OUString& rRefString, SCTAB nDefTab, + ScRefAddress& rStartRefAddress, ScRefAddress& rEndRefAddress, + const ScAddress::Details& rDetails, + ScAddress::ExternalInfo* pExtInfo /* = NULL */ ) +{ + bool bRet = false; + if (pExtInfo || (ScGlobal::FindUnquoted( rRefString, SC_COMPILER_FILE_TAB_SEP) == -1)) + { + ScRange aRange( ScAddress( 0, 0, nDefTab)); + ScRefFlags nRes = aRange.Parse( rRefString, rDoc, rDetails, pExtInfo); + if ( nRes & ScRefFlags::VALID ) + { + rStartRefAddress.Set( aRange.aStart, + ((nRes & ScRefFlags::COL_ABS) == ScRefFlags::ZERO), + ((nRes & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO), + ((nRes & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO)); + rEndRefAddress.Set( aRange.aEnd, + ((nRes & ScRefFlags::COL2_ABS) == ScRefFlags::ZERO), + ((nRes & ScRefFlags::ROW2_ABS) == ScRefFlags::ZERO), + ((nRes & ScRefFlags::TAB2_ABS) == ScRefFlags::ZERO)); + bRet = true; + } + } + return bRet; +} + +ScRefFlags ScAddress::Parse( const OUString& r, const ScDocument& rDoc, + const Details& rDetails, + ExternalInfo* pExtInfo, + const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks, + sal_Int32* pSheetEndPos, + const OUString* pErrRef ) +{ + return lcl_ScAddress_Parse( r.getStr(), rDoc, *this, rDetails, pExtInfo, pExternalLinks, pSheetEndPos, pErrRef); +} + +ScRange ScRange::Intersection( const ScRange& rOther ) const +{ + SCCOL nCol1 = std::max(aStart.Col(), rOther.aStart.Col()); + SCCOL nCol2 = std::min(aEnd.Col(), rOther.aEnd.Col()); + SCROW nRow1 = std::max(aStart.Row(), rOther.aStart.Row()); + SCROW nRow2 = std::min(aEnd.Row(), rOther.aEnd.Row()); + SCTAB nTab1 = std::max(aStart.Tab(), rOther.aStart.Tab()); + SCTAB nTab2 = std::min(aEnd.Tab(), rOther.aEnd.Tab()); + + if (nCol1 > nCol2 || nRow1 > nRow2 || nTab1 > nTab2) + return ScRange(ScAddress::INITIALIZE_INVALID); + + return ScRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); +} + +void ScRange::ExtendTo( const ScRange& rRange ) +{ + OSL_ENSURE( rRange.IsValid(), "ScRange::ExtendTo - cannot extend to invalid range" ); + if( IsValid() ) + { + aStart.SetCol( std::min( aStart.Col(), rRange.aStart.Col() ) ); + aStart.SetRow( std::min( aStart.Row(), rRange.aStart.Row() ) ); + aStart.SetTab( std::min( aStart.Tab(), rRange.aStart.Tab() ) ); + aEnd.SetCol( std::max( aEnd.Col(), rRange.aEnd.Col() ) ); + aEnd.SetRow( std::max( aEnd.Row(), rRange.aEnd.Row() ) ); + aEnd.SetTab( std::max( aEnd.Tab(), rRange.aEnd.Tab() ) ); + } + else + *this = rRange; +} + +static ScRefFlags lcl_ScRange_Parse_OOo( ScRange& rRange, + const OUString& r, + const ScDocument& rDoc, + ScAddress::ExternalInfo* pExtInfo, + const OUString* pErrRef ) +{ + ScRefFlags nRes1 = ScRefFlags::ZERO, nRes2 = ScRefFlags::ZERO; + sal_Int32 nPos = ScGlobal::FindUnquoted( r, ':'); + if (nPos != -1) + { + OUStringBuffer aTmp(r); + aTmp[nPos] = 0; + const sal_Unicode* p = aTmp.getStr(); + ScRefFlags nRawRes1 = ScRefFlags::ZERO; + nRes1 = lcl_ScAddress_Parse_OOo( p, rDoc, rRange.aStart, nRawRes1, pExtInfo, nullptr, nullptr, pErrRef); + if ((nRes1 != ScRefFlags::ZERO) || + ((nRawRes1 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)) && + (nRawRes1 & ScRefFlags::TAB_VALID))) + { + rRange.aEnd = rRange.aStart; // sheet must be initialized identical to first sheet + ScRefFlags nRawRes2 = ScRefFlags::ZERO; + nRes2 = lcl_ScAddress_Parse_OOo( p + nPos+ 1, rDoc, rRange.aEnd, nRawRes2, + pExtInfo, &rRange, nullptr, pErrRef); + if (!((nRes1 & ScRefFlags::VALID) && (nRes2 & ScRefFlags::VALID)) && + // If not fully valid addresses, check if both have a valid + // column or row, and both have valid (or omitted) sheet references. + (nRawRes1 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)) && + (nRawRes1 & ScRefFlags::TAB_VALID) && + (nRawRes2 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)) && + (nRawRes2 & ScRefFlags::TAB_VALID) && + // Both must be column XOR row references, A:A or 1:1 but not A:1 or 1:A + ((nRawRes1 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)) == + (nRawRes2 & (ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID)))) + { + nRes1 = nRawRes1 | ScRefFlags::VALID; + nRes2 = nRawRes2 | ScRefFlags::VALID; + if (nRawRes1 & ScRefFlags::COL_VALID) + { + rRange.aStart.SetRow(0); + rRange.aEnd.SetRow(rDoc.MaxRow()); + nRes1 |= ScRefFlags::ROW_VALID | ScRefFlags::ROW_ABS; + nRes2 |= ScRefFlags::ROW_VALID | ScRefFlags::ROW_ABS; + } + else + { + rRange.aStart.SetCol(0); + rRange.aEnd.SetCol( rDoc.MaxCol() ); + nRes1 |= ScRefFlags::COL_VALID | ScRefFlags::COL_ABS; + nRes2 |= ScRefFlags::COL_VALID | ScRefFlags::COL_ABS; + } + } + else if ((nRes1 & ScRefFlags::VALID) && (nRes2 & ScRefFlags::VALID)) + { + // Flag entire column/row references so they can be displayed + // as such. If the sticky reference parts are not both + // absolute or relative, assume that the user thought about + // something we should not touch. + if (rRange.aStart.Row() == 0 && rRange.aEnd.Row() == rDoc.MaxRow() && + ((nRes1 & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO) && + ((nRes2 & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO)) + { + nRes1 |= ScRefFlags::ROW_ABS; + nRes2 |= ScRefFlags::ROW_ABS; + } + else if (rRange.aStart.Col() == 0 && rRange.aEnd.Col() == rDoc.MaxCol() && + ((nRes1 & ScRefFlags::COL_ABS) == ScRefFlags::ZERO) && ((nRes2 & ScRefFlags::COL_ABS) == ScRefFlags::ZERO)) + { + nRes1 |= ScRefFlags::COL_ABS; + nRes2 |= ScRefFlags::COL_ABS; + } + } + if ((nRes1 & ScRefFlags::VALID) && (nRes2 & ScRefFlags::VALID)) + { + // PutInOrder / Justify + ScRefFlags nMask, nBits1, nBits2; + SCCOL nTempCol; + if ( rRange.aEnd.Col() < (nTempCol = rRange.aStart.Col()) ) + { + rRange.aStart.SetCol(rRange.aEnd.Col()); rRange.aEnd.SetCol(nTempCol); + nMask = (ScRefFlags::COL_VALID | ScRefFlags::COL_ABS); + nBits1 = nRes1 & nMask; + nBits2 = nRes2 & nMask; + nRes1 = (nRes1 & ~nMask) | nBits2; + nRes2 = (nRes2 & ~nMask) | nBits1; + } + SCROW nTempRow; + if ( rRange.aEnd.Row() < (nTempRow = rRange.aStart.Row()) ) + { + rRange.aStart.SetRow(rRange.aEnd.Row()); rRange.aEnd.SetRow(nTempRow); + nMask = (ScRefFlags::ROW_VALID | ScRefFlags::ROW_ABS); + nBits1 = nRes1 & nMask; + nBits2 = nRes2 & nMask; + nRes1 = (nRes1 & ~nMask) | nBits2; + nRes2 = (nRes2 & ~nMask) | nBits1; + } + SCTAB nTempTab; + if ( rRange.aEnd.Tab() < (nTempTab = rRange.aStart.Tab()) ) + { + rRange.aStart.SetTab(rRange.aEnd.Tab()); rRange.aEnd.SetTab(nTempTab); + nMask = (ScRefFlags::TAB_VALID | ScRefFlags::TAB_ABS | ScRefFlags::TAB_3D); + nBits1 = nRes1 & nMask; + nBits2 = nRes2 & nMask; + nRes1 = (nRes1 & ~nMask) | nBits2; + nRes2 = (nRes2 & ~nMask) | nBits1; + } + if ( ((nRes1 & ( ScRefFlags::TAB_ABS | ScRefFlags::TAB_3D )) + == ( ScRefFlags::TAB_ABS | ScRefFlags::TAB_3D )) + && !(nRes2 & ScRefFlags::TAB_3D) ) + nRes2 |= ScRefFlags::TAB_ABS; + } + else + { + // Don't leave around valid half references. + nRes1 = nRes2 = ScRefFlags::ZERO; + } + } + } + applyStartToEndFlags(nRes1, nRes2 & ScRefFlags::BITS); + nRes1 |= nRes2 & ScRefFlags::VALID; + return nRes1; +} + +ScRefFlags ScRange::Parse( const OUString& rString, const ScDocument& rDoc, + const ScAddress::Details& rDetails, + ScAddress::ExternalInfo* pExtInfo, + const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks, + const OUString* pErrRef ) +{ + if (rString.isEmpty()) + return ScRefFlags::ZERO; + + switch (rDetails.eConv) + { + case formula::FormulaGrammar::CONV_XL_A1: + case formula::FormulaGrammar::CONV_XL_OOX: + { + return lcl_ScRange_Parse_XL_A1( *this, rString.getStr(), rDoc, false, pExtInfo, + (rDetails.eConv == formula::FormulaGrammar::CONV_XL_OOX ? pExternalLinks : nullptr), + nullptr, pErrRef ); + } + + case formula::FormulaGrammar::CONV_XL_R1C1: + { + return lcl_ScRange_Parse_XL_R1C1( *this, rString.getStr(), rDoc, rDetails, false, pExtInfo, nullptr ); + } + + default: + case formula::FormulaGrammar::CONV_OOO: + { + return lcl_ScRange_Parse_OOo( *this, rString, rDoc, pExtInfo, pErrRef ); + } + } +} + +// Accept a full range, or an address +ScRefFlags ScRange::ParseAny( const OUString& rString, const ScDocument& rDoc, + const ScAddress::Details& rDetails ) +{ + ScRefFlags nRet = Parse( rString, rDoc, rDetails ); + const ScRefFlags nValid = ScRefFlags::VALID | ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID; + + if ( (nRet & nValid) != nValid ) + { + ScAddress aAdr(aStart);//initialize with currentPos as fallback for table number + nRet = aAdr.Parse( rString, rDoc, rDetails ); + if ( nRet & ScRefFlags::VALID ) + aStart = aEnd = aAdr; + } + return nRet; +} + +// Parse only full row references +ScRefFlags ScRange::ParseCols( const ScDocument& rDoc, + const OUString& rStr, + const ScAddress::Details& rDetails ) +{ + if (rStr.isEmpty()) + return ScRefFlags::ZERO; + + const sal_Unicode* p = rStr.getStr(); + ScRefFlags nRes = ScRefFlags::ZERO; + ScRefFlags ignored = ScRefFlags::ZERO; + + switch (rDetails.eConv) + { + default : + case formula::FormulaGrammar::CONV_OOO: // No full col refs in OOO yet, assume XL notation + case formula::FormulaGrammar::CONV_XL_A1: + case formula::FormulaGrammar::CONV_XL_OOX: + if (nullptr != (p = lcl_a1_get_col( rDoc, p, &aStart, &ignored, nullptr) ) ) + { + if( p[0] == ':') + { + if( nullptr != (p = lcl_a1_get_col( rDoc, p+1, &aEnd, &ignored, nullptr))) + { + nRes = ScRefFlags::COL_VALID; + } + } + else + { + aEnd = aStart; + nRes = ScRefFlags::COL_VALID; + } + } + break; + + case formula::FormulaGrammar::CONV_XL_R1C1: + if ((p[0] == 'C' || p[0] == 'c') && + nullptr != (p = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p, rDetails, &aStart, &ignored ))) + { + if( p[0] == ':') + { + if( (p[1] == 'C' || p[1] == 'c') && + nullptr != (p = lcl_r1c1_get_col( rDoc.GetSheetLimits(), p+1, rDetails, &aEnd, &ignored ))) + { + nRes = ScRefFlags::COL_VALID; + } + } + else + { + aEnd = aStart; + nRes = ScRefFlags::COL_VALID; + } + } + break; + } + + return (p != nullptr && *p == '\0') ? nRes : ScRefFlags::ZERO; +} + +// Parse only full row references +void ScRange::ParseRows( const ScDocument& rDoc, + const OUString& rStr, + const ScAddress::Details& rDetails ) +{ + if (rStr.isEmpty()) + return; + + const sal_Unicode* p = rStr.getStr(); + ScRefFlags ignored = ScRefFlags::ZERO; + + switch (rDetails.eConv) + { + default : + case formula::FormulaGrammar::CONV_OOO: // No full row refs in OOO yet, assume XL notation + case formula::FormulaGrammar::CONV_XL_A1: + case formula::FormulaGrammar::CONV_XL_OOX: + if (nullptr != (p = lcl_a1_get_row( rDoc, p, &aStart, &ignored, nullptr) ) ) + { + if( p[0] == ':') + { + lcl_a1_get_row( rDoc, p+1, &aEnd, &ignored, nullptr); + } + else + { + aEnd = aStart; + } + } + break; + + case formula::FormulaGrammar::CONV_XL_R1C1: + if ((p[0] == 'R' || p[0] == 'r') && + nullptr != (p = lcl_r1c1_get_row( rDoc.GetSheetLimits(), p, rDetails, &aStart, &ignored ))) + { + if( p[0] == ':') + { + if( p[1] == 'R' || p[1] == 'r' ) + { + lcl_r1c1_get_row( rDoc.GetSheetLimits(), p+1, rDetails, &aEnd, &ignored ); + } + } + else + { + aEnd = aStart; + } + } + break; + } +} + +template<typename T > static void lcl_ScColToAlpha( T& rBuf, SCCOL nCol ) +{ + if (nCol < 26*26) + { + if (nCol < 26) + rBuf.append( static_cast<char>( 'A' + nCol )); + else + { + rBuf.append( static_cast<char>( 'A' + nCol / 26 - 1 )); + rBuf.append( static_cast<char>( 'A' + nCol % 26 )); + } + } + else + { + sal_Int32 nInsert = rBuf.getLength(); + while (nCol >= 26) + { + SCCOL nC = nCol % 26; + rBuf.insert(nInsert, static_cast<char> ( 'A' + nC )); + nCol = sal::static_int_cast<SCCOL>( nCol - nC ); + nCol = nCol / 26 - 1; + } + rBuf.insert(nInsert, static_cast<char> ( 'A' + nCol )); + } +} + +void ScColToAlpha( OUStringBuffer& rBuf, SCCOL nCol) +{ + lcl_ScColToAlpha(rBuf, nCol); +} + +template <typename T > static void lcl_a1_append_c ( T &rString, int nCol, bool bIsAbs ) +{ + if( bIsAbs ) + rString.append("$"); + lcl_ScColToAlpha( rString, sal::static_int_cast<SCCOL>(nCol) ); +} + +template <typename T > static void lcl_a1_append_r ( T &rString, sal_Int32 nRow, bool bIsAbs ) +{ + if ( bIsAbs ) + rString.append("$"); + rString.append( nRow + 1 ); +} + +template <typename T > static void lcl_r1c1_append_c ( T &rString, sal_Int32 nCol, bool bIsAbs, + const ScAddress::Details& rDetails ) +{ + rString.append("C"); + if (bIsAbs) + { + rString.append( nCol + 1 ); + } + else + { + nCol -= rDetails.nCol; + if (nCol != 0) { + rString.append("[").append(nCol).append("]"); + } + } +} + +template <typename T > static void lcl_r1c1_append_r ( T &rString, sal_Int32 nRow, bool bIsAbs, + const ScAddress::Details& rDetails ) +{ + rString.append("R"); + if (bIsAbs) + { + rString.append( nRow + 1 ); + } + else + { + nRow -= rDetails.nRow; + if (nRow != 0) { + rString.append("[").append(nRow).append("]"); + } + } +} + +static OUString getFileNameFromDoc( const ScDocument* pDoc ) +{ + // TODO : er points at ScGlobal::GetAbsDocName() + // as a better template. Look into it + OUString sFileName; + SfxObjectShell* pShell; + + if( nullptr != pDoc && + nullptr != (pShell = pDoc->GetDocumentShell() ) ) + { + uno::Reference< frame::XModel > xModel = pShell->GetModel(); + if( xModel.is() ) + { + if( !xModel->getURL().isEmpty() ) + { + INetURLObject aURL( xModel->getURL() ); + sFileName = aURL.GetLastName(); + } + else + sFileName = pShell->GetTitle(); + } + } + return sFileName; +} + + +static void lcl_string_append(OUStringBuffer &rString, std::u16string_view sString) +{ + rString.append(sString); +} + +static void lcl_string_append(OStringBuffer &rString, std::u16string_view sString) +{ + rString.append(OUStringToOString( sString, RTL_TEXTENCODING_UTF8 )); +} + +template<typename T > static void lcl_Format( T& r, SCTAB nTab, SCROW nRow, SCCOL nCol, ScRefFlags nFlags, + const ScDocument* pDoc, + const ScAddress::Details& rDetails) +{ + if( nFlags & ScRefFlags::VALID ) + nFlags |= ScRefFlags::ROW_VALID | ScRefFlags::COL_VALID | ScRefFlags::TAB_VALID; + if( pDoc && (nFlags & ScRefFlags::TAB_VALID ) ) + { + if ( nTab < 0 || nTab >= pDoc->GetTableCount() ) + { + lcl_string_append(r, ScCompiler::GetNativeSymbol(ocErrRef)); + return; + } + if( nFlags & ScRefFlags::TAB_3D ) + { + OUString aTabName, aDocName; + pDoc->GetName(nTab, aTabName); + assert( !aTabName.isEmpty() && "empty sheet name"); + // External Reference, same as in ScCompiler::MakeTabStr() + if( aTabName[0] == '\'' ) + { // "'Doc'#Tab" + sal_Int32 nPos = ScCompiler::GetDocTabPos( aTabName); + if (nPos != -1) + { + aDocName = aTabName.copy( 0, nPos + 1 ); + aTabName = aTabName.copy( nPos + 1 ); + } + } + else if( nFlags & ScRefFlags::FORCE_DOC ) + { + // VBA has an 'external' flag that forces the addition of the + // tab name _and_ the doc name. The VBA code would be + // needlessly complicated if it constructed an actual external + // reference so we add this somewhat cheesy kludge to force the + // addition of the document name even for non-external references + aDocName = getFileNameFromDoc( pDoc ); + } + ScCompiler::CheckTabQuotes( aTabName, rDetails.eConv); + + switch( rDetails.eConv ) + { + default : + case formula::FormulaGrammar::CONV_OOO: + lcl_string_append(r, aDocName); + if( nFlags & ScRefFlags::TAB_ABS ) + r.append("$"); + lcl_string_append(r, aTabName); + r.append("."); + break; + + case formula::FormulaGrammar::CONV_XL_OOX: + if (!aTabName.isEmpty() && aTabName[0] == '\'') + { + if (!aDocName.isEmpty()) + { + lcl_string_append(r.append("'["), aDocName); + r.append("]"); + lcl_string_append(r, aTabName.subView(1)); + } + else + { + lcl_string_append(r, aTabName); + } + r.append("!"); + break; + } + [[fallthrough]]; + case formula::FormulaGrammar::CONV_XL_A1: + case formula::FormulaGrammar::CONV_XL_R1C1: + if (!aDocName.isEmpty()) + { + lcl_string_append(r.append("["), aDocName); + r.append("]"); + } + lcl_string_append(r, aTabName); + r.append("!"); + break; + } + } + } + switch( rDetails.eConv ) + { + default : + case formula::FormulaGrammar::CONV_OOO: + case formula::FormulaGrammar::CONV_XL_A1: + case formula::FormulaGrammar::CONV_XL_OOX: + if( nFlags & ScRefFlags::COL_VALID ) + lcl_a1_append_c ( r, nCol, (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO ); + if( nFlags & ScRefFlags::ROW_VALID ) + lcl_a1_append_r ( r, nRow, (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO ); + break; + + case formula::FormulaGrammar::CONV_XL_R1C1: + if( nFlags & ScRefFlags::ROW_VALID ) + lcl_r1c1_append_r ( r, nRow, (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO, rDetails ); + if( nFlags & ScRefFlags::COL_VALID ) + lcl_r1c1_append_c ( r, nCol, (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO, rDetails ); + break; + } +} + +void ScAddress::Format( OStringBuffer& r, ScRefFlags nFlags, + const ScDocument* pDoc, + const Details& rDetails) const +{ + lcl_Format(r, nTab, nRow, nCol, nFlags, pDoc, rDetails); +} + +OUString ScAddress::Format(ScRefFlags nFlags, const ScDocument* pDoc, + const Details& rDetails) const +{ + OUStringBuffer r; + lcl_Format(r, nTab, nRow, nCol, nFlags, pDoc, rDetails); + return r.makeStringAndClear(); +} + +static void lcl_Split_DocTab( const ScDocument& rDoc, SCTAB nTab, + const ScAddress::Details& rDetails, + ScRefFlags nFlags, + OUString& rTabName, OUString& rDocName ) +{ + rDoc.GetName(nTab, rTabName); + rDocName.clear(); + // External reference, same as in ScCompiler::MakeTabStr() + if (!rTabName.isEmpty() && rTabName[0] == '\'') + { // "'Doc'#Tab" + sal_Int32 nPos = ScCompiler::GetDocTabPos( rTabName); + if (nPos != -1) + { + rDocName = rTabName.copy( 0, nPos + 1 ); + rTabName = rTabName.copy( nPos + 1 ); + } + } + else if( nFlags & ScRefFlags::FORCE_DOC ) + { + // VBA has an 'external' flag that forces the addition of the + // tab name _and_ the doc name. The VBA code would be + // needlessly complicated if it constructed an actual external + // reference so we add this somewhat cheesy kludge to force the + // addition of the document name even for non-external references + rDocName = getFileNameFromDoc(&rDoc); + } + ScCompiler::CheckTabQuotes( rTabName, rDetails.eConv); +} + +static void lcl_ScRange_Format_XL_Header( OUStringBuffer& rString, const ScRange& rRange, + ScRefFlags nFlags, const ScDocument& rDoc, + const ScAddress::Details& rDetails ) +{ + if( !(nFlags & ScRefFlags::TAB_3D) ) + return; + + OUString aTabName, aDocName; + lcl_Split_DocTab( rDoc, rRange.aStart.Tab(), rDetails, nFlags, aTabName, aDocName ); + switch (rDetails.eConv) + { + case formula::FormulaGrammar::CONV_XL_OOX: + if (!aTabName.isEmpty() && aTabName[0] == '\'') + { + if (!aDocName.isEmpty()) + { + rString.append("'[" + aDocName + "]" + aTabName.subView(1)); + } + else + { + rString.append(aTabName); + } + break; + } + [[fallthrough]]; + default: + if (!aDocName.isEmpty()) + { + rString.append("[" + aDocName + "]"); + } + rString.append(aTabName); + break; + } + if( nFlags & ScRefFlags::TAB2_3D ) + { + lcl_Split_DocTab( rDoc, rRange.aEnd.Tab(), rDetails, nFlags, aTabName, aDocName ); + rString.append(":"); + rString.append(aTabName); + } + rString.append("!"); +} + +// helpers used in ScRange::Format +static bool lcl_ColAbsFlagDiffer(const ScRefFlags nFlags) +{ + return static_cast<bool>(nFlags & ScRefFlags::COL_ABS) != static_cast<bool>(nFlags & ScRefFlags::COL2_ABS); +} +static bool lcl_RowAbsFlagDiffer(const ScRefFlags nFlags) +{ + return static_cast<bool>(nFlags & ScRefFlags::ROW_ABS) != static_cast<bool>(nFlags & ScRefFlags::ROW2_ABS); +} + +OUString ScRange::Format( const ScDocument& rDoc, ScRefFlags nFlags, + const ScAddress::Details& rDetails, bool bFullAddressNotation ) const +{ + if( !( nFlags & ScRefFlags::VALID ) ) + { + return ScCompiler::GetNativeSymbol(ocErrRef); + } + + OUStringBuffer r; + switch( rDetails.eConv ) { + default : + case formula::FormulaGrammar::CONV_OOO: { + bool bOneTab = (aStart.Tab() == aEnd.Tab()); + if ( !bOneTab ) + nFlags |= ScRefFlags::TAB_3D; + r = aStart.Format(nFlags, &rDoc, rDetails); + if( aStart != aEnd || + lcl_ColAbsFlagDiffer( nFlags ) || + lcl_RowAbsFlagDiffer( nFlags )) + { + const ScDocument* pDoc = &rDoc; + // move flags of end reference to start reference, mask with BITS to exclude FORCE_DOC flag + nFlags = ScRefFlags::VALID | (ScRefFlags(o3tl::to_underlying(nFlags) >> 4) & ScRefFlags::BITS); + if ( bOneTab ) + pDoc = nullptr; + else + nFlags |= ScRefFlags::TAB_3D; + r.append(":" + aEnd.Format(nFlags, pDoc, rDetails)); + } + break; + } + + case formula::FormulaGrammar::CONV_XL_A1: + case formula::FormulaGrammar::CONV_XL_OOX: { + SCCOL nMaxCol = rDoc.MaxCol(); + SCROW nMaxRow = rDoc.MaxRow(); + + lcl_ScRange_Format_XL_Header( r, *this, nFlags, rDoc, rDetails ); + if( aStart.Col() == 0 && aEnd.Col() >= nMaxCol && !bFullAddressNotation ) + { + // Full col refs always require 2 rows (2:2) + lcl_a1_append_r( r, aStart.Row(), (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO ); + r.append(":"); + lcl_a1_append_r( r, aEnd.Row(), (nFlags & ScRefFlags::ROW2_ABS) != ScRefFlags::ZERO ); + } + else if( aStart.Row() == 0 && aEnd.Row() >= nMaxRow && !bFullAddressNotation ) + { + // Full row refs always require 2 cols (A:A) + lcl_a1_append_c( r, aStart.Col(), (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO ); + r.append(":"); + lcl_a1_append_c( r, aEnd.Col(), (nFlags & ScRefFlags::COL2_ABS) != ScRefFlags::ZERO ); + } + else + { + lcl_a1_append_c ( r, aStart.Col(), (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO ); + lcl_a1_append_r ( r, aStart.Row(), (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO ); + if( aStart.Col() != aEnd.Col() || + lcl_ColAbsFlagDiffer( nFlags ) || + aStart.Row() != aEnd.Row() || + lcl_RowAbsFlagDiffer( nFlags ) ) { + r.append(":"); + lcl_a1_append_c ( r, aEnd.Col(), (nFlags & ScRefFlags::COL2_ABS) != ScRefFlags::ZERO ); + lcl_a1_append_r ( r, aEnd.Row(), (nFlags & ScRefFlags::ROW2_ABS) != ScRefFlags::ZERO ); + } + } + break; + } + + case formula::FormulaGrammar::CONV_XL_R1C1: { + SCCOL nMaxCol = rDoc.MaxCol(); + SCROW nMaxRow = rDoc.MaxRow(); + + lcl_ScRange_Format_XL_Header( r, *this, nFlags, rDoc, rDetails ); + if( aStart.Col() == 0 && aEnd.Col() >= nMaxCol && !bFullAddressNotation ) + { + lcl_r1c1_append_r( r, aStart.Row(), (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO, rDetails ); + if( aStart.Row() != aEnd.Row() || + lcl_RowAbsFlagDiffer( nFlags ) ) { + r.append(":"); + lcl_r1c1_append_r( r, aEnd.Row(), (nFlags & ScRefFlags::ROW2_ABS) != ScRefFlags::ZERO, rDetails ); + } + } + else if( aStart.Row() == 0 && aEnd.Row() >= nMaxRow && !bFullAddressNotation ) + { + lcl_r1c1_append_c( r, aStart.Col(), (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO, rDetails ); + if( aStart.Col() != aEnd.Col() || + lcl_ColAbsFlagDiffer( nFlags )) { + r.append(":"); + lcl_r1c1_append_c( r, aEnd.Col(), (nFlags & ScRefFlags::COL2_ABS) != ScRefFlags::ZERO, rDetails ); + } + } + else + { + lcl_r1c1_append_r( r, aStart.Row(), (nFlags & ScRefFlags::ROW_ABS) != ScRefFlags::ZERO, rDetails ); + lcl_r1c1_append_c( r, aStart.Col(), (nFlags & ScRefFlags::COL_ABS) != ScRefFlags::ZERO, rDetails ); + if( aStart.Col() != aEnd.Col() || + lcl_ColAbsFlagDiffer( nFlags ) || + aStart.Row() != aEnd.Row() || + lcl_RowAbsFlagDiffer( nFlags ) ) { + r.append(":"); + lcl_r1c1_append_r( r, aEnd.Row(), (nFlags & ScRefFlags::ROW2_ABS) != ScRefFlags::ZERO, rDetails ); + lcl_r1c1_append_c( r, aEnd.Col(), (nFlags & ScRefFlags::COL2_ABS) != ScRefFlags::ZERO, rDetails ); + } + } + break; + } + } + return r.makeStringAndClear(); +} + +bool ScAddress::Move( SCCOL dx, SCROW dy, SCTAB dz, ScAddress& rErrorPos, const ScDocument& rDoc ) +{ + SCTAB nMaxTab = rDoc.GetTableCount(); + SCCOL nMaxCol = rDoc.MaxCol(); + SCROW nMaxRow = rDoc.MaxRow(); + dx = Col() + dx; + dy = Row() + dy; + dz = Tab() + dz; + bool bValid = true; + rErrorPos.SetCol(dx); + if( dx < 0 ) + { + dx = 0; + bValid = false; + } + else if( dx > nMaxCol ) + { + dx = nMaxCol; + bValid =false; + } + rErrorPos.SetRow(dy); + if( dy < 0 ) + { + dy = 0; + bValid = false; + } + else if( dy > nMaxRow ) + { + dy = nMaxRow; + bValid =false; + } + rErrorPos.SetTab(dz); + if( dz < 0 ) + { + dz = 0; + bValid = false; + } + else if( dz > nMaxTab ) + { + // Always set MAXTAB+1 so further checks without ScDocument detect invalid. + rErrorPos.SetTab(MAXTAB+1); + dz = nMaxTab; + bValid =false; + } + Set( dx, dy, dz ); + return bValid; +} + +bool ScRange::Move( SCCOL dx, SCROW dy, SCTAB dz, ScRange& rErrorRange, const ScDocument& rDoc ) +{ + SCCOL nMaxCol = rDoc.MaxCol(); + SCROW nMaxRow = rDoc.MaxRow(); + if (dy && aStart.Row() == 0 && aEnd.Row() == nMaxRow) + dy = 0; // Entire column not to be moved. + if (dx && aStart.Col() == 0 && aEnd.Col() == nMaxCol) + dx = 0; // Entire row not to be moved. + bool b = aStart.Move( dx, dy, dz, rErrorRange.aStart, rDoc ); + b &= aEnd.Move( dx, dy, dz, rErrorRange.aEnd, rDoc ); + return b; +} + +bool ScRange::MoveSticky( const ScDocument& rDoc, SCCOL dx, SCROW dy, SCTAB dz, ScRange& rErrorRange ) +{ + const SCCOL nMaxCol = rDoc.MaxCol(); + const SCROW nMaxRow = rDoc.MaxRow(); + bool bColRange = (aStart.Col() < aEnd.Col()); + bool bRowRange = (aStart.Row() < aEnd.Row()); + if (dy && aStart.Row() == 0 && aEnd.Row() == nMaxRow) + dy = 0; // Entire column not to be moved. + if (dx && aStart.Col() == 0 && aEnd.Col() == nMaxCol) + dx = 0; // Entire row not to be moved. + bool b1 = aStart.Move( dx, dy, dz, rErrorRange.aStart, rDoc ); + if (dx && bColRange && aEnd.Col() == nMaxCol) + dx = 0; // End column sticky. + if (dy && bRowRange && aEnd.Row() == nMaxRow) + dy = 0; // End row sticky. + SCTAB nOldTab = aEnd.Tab(); + bool b2 = aEnd.Move( dx, dy, dz, rErrorRange.aEnd, rDoc ); + if (!b2) + { + // End column or row of a range may have become sticky. + bColRange = (!dx || (bColRange && aEnd.Col() == nMaxCol)); + if (dx && bColRange) + rErrorRange.aEnd.SetCol(nMaxCol); + bRowRange = (!dy || (bRowRange && aEnd.Row() == nMaxRow)); + if (dy && bRowRange) + rErrorRange.aEnd.SetRow(nMaxRow); + b2 = bColRange && bRowRange && (aEnd.Tab() - nOldTab == dz); + } + return b1 && b2; +} + +void ScRange::IncColIfNotLessThan(const ScDocument& rDoc, SCCOL nStartCol, SCCOL nOffset) +{ + if (aStart.Col() >= nStartCol) + { + aStart.IncCol(nOffset); + if (aStart.Col() < 0) + aStart.SetCol(0); + else if(aStart.Col() > rDoc.MaxCol()) + aStart.SetCol(rDoc.MaxCol()); + } + if (aEnd.Col() >= nStartCol) + { + aEnd.IncCol(nOffset); + if (aEnd.Col() < 0) + aEnd.SetCol(0); + else if(aEnd.Col() > rDoc.MaxCol()) + aEnd.SetCol(rDoc.MaxCol()); + } +} + +void ScRange::IncRowIfNotLessThan(const ScDocument& rDoc, SCROW nStartRow, SCROW nOffset) +{ + if (aStart.Row() >= nStartRow) + { + aStart.IncRow(nOffset); + if (aStart.Row() < 0) + aStart.SetRow(0); + else if(aStart.Row() > rDoc.MaxRow()) + aStart.SetRow(rDoc.MaxRow()); + } + if (aEnd.Row() >= nStartRow) + { + aEnd.IncRow(nOffset); + if (aEnd.Row() < 0) + aEnd.SetRow(0); + else if(aEnd.Row() > rDoc.MaxRow()) + aEnd.SetRow(rDoc.MaxRow()); + } +} + +bool ScRange::IsEndColSticky( const ScDocument& rDoc ) const +{ + // Only in an actual column range, i.e. not if both columns are MAXCOL. + return aEnd.Col() == rDoc.MaxCol() && aStart.Col() < aEnd.Col(); +} + +bool ScRange::IsEndRowSticky( const ScDocument& rDoc ) const +{ + // Only in an actual row range, i.e. not if both rows are MAXROW. + return aEnd.Row() == rDoc.MaxRow() && aStart.Row() < aEnd.Row(); +} + +void ScRange::IncEndColSticky( const ScDocument& rDoc, SCCOL nDelta ) +{ + SCCOL nCol = aEnd.Col(); + if (aStart.Col() >= nCol) + { + // Less than two columns => not sticky. + aEnd.IncCol( nDelta); + return; + } + + const SCCOL nMaxCol = rDoc.MaxCol(); + if (nCol == nMaxCol) + // already sticky + return; + + if (nCol < nMaxCol) + aEnd.SetCol( ::std::min( static_cast<SCCOL>(nCol + nDelta), nMaxCol)); + else + aEnd.IncCol( nDelta); // was greater than nMaxCol, caller should know... +} + +void ScRange::IncEndRowSticky( const ScDocument& rDoc, SCROW nDelta ) +{ + SCROW nRow = aEnd.Row(); + if (aStart.Row() >= nRow) + { + // Less than two rows => not sticky. + aEnd.IncRow( nDelta); + return; + } + + if (nRow == rDoc.MaxRow()) + // already sticky + return; + + if (nRow < rDoc.MaxRow()) + aEnd.SetRow( ::std::min( static_cast<SCROW>(nRow + nDelta), rDoc.MaxRow())); + else + aEnd.IncRow( nDelta); // was greater than rDoc.MaxRow(), caller should know... +} + +OUString ScAddress::GetColRowString() const +{ + OUStringBuffer aString; + + switch( detailsOOOa1.eConv ) + { + default : + case formula::FormulaGrammar::CONV_OOO: + case formula::FormulaGrammar::CONV_XL_A1: + case formula::FormulaGrammar::CONV_XL_OOX: + lcl_ScColToAlpha( aString, nCol); + aString.append(nRow+1); + break; + + case formula::FormulaGrammar::CONV_XL_R1C1: + lcl_r1c1_append_r ( aString, nRow, false/*bAbsolute*/, detailsOOOa1 ); + lcl_r1c1_append_c ( aString, nCol, false/*bAbsolute*/, detailsOOOa1 ); + break; + } + + return aString.makeStringAndClear(); +} + +OUString ScRefAddress::GetRefString( const ScDocument& rDoc, SCTAB nActTab, + const ScAddress::Details& rDetails ) const +{ + if ( Tab()+1 > rDoc.GetTableCount() ) + return ScCompiler::GetNativeSymbol(ocErrRef); + + ScRefFlags nFlags = ScRefFlags::VALID; + if ( nActTab != Tab() ) + { + nFlags |= ScRefFlags::TAB_3D; + if ( !bRelTab ) + nFlags |= ScRefFlags::TAB_ABS; + } + if ( !bRelCol ) + nFlags |= ScRefFlags::COL_ABS; + if ( !bRelRow ) + nFlags |= ScRefFlags::ROW_ABS; + + return aAdr.Format(nFlags, &rDoc, rDetails); +} + +bool AlphaToCol(const ScDocument& rDoc, SCCOL& rCol, std::u16string_view rStr) +{ + SCCOL nResult = 0; + sal_Int32 nStop = rStr.size(); + sal_Int32 nPos = 0; + sal_Unicode c; + const SCCOL nMaxCol = rDoc.MaxCol(); + while (nResult <= nMaxCol && nPos < nStop && (c = rStr[nPos]) != 0 && + rtl::isAsciiAlpha(c)) + { + if (nPos > 0) + nResult = (nResult + 1) * 26; + nResult += ScGlobal::ToUpperAlpha(c) - 'A'; + ++nPos; + } + bool bOk = (rDoc.ValidCol(nResult) && nPos > 0); + if (bOk) + rCol = nResult; + return bOk; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/adiasync.cxx b/sc/source/core/tool/adiasync.cxx new file mode 100644 index 0000000000..ba2b49eb38 --- /dev/null +++ b/sc/source/core/tool/adiasync.cxx @@ -0,0 +1,137 @@ +/* -*- 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 <sal/config.h> + +#include <algorithm> + +#include <sfx2/objsh.hxx> + +#include <adiasync.hxx> +#include <brdcst.hxx> +#include <document.hxx> +#include <docsh.hxx> +#include <osl/diagnose.h> +#include <osl/thread.h> + +ScAddInAsyncs theAddInAsyncTbl; + +extern "C" { +void CALLTYPE ScAddInAsyncCallBack( double& nHandle, void* pData ) +{ + ScAddInAsync::CallBack( sal_uLong( nHandle ), pData ); +} +} + +ScAddInAsync::ScAddInAsync(sal_uLong nHandleP, LegacyFuncData* pFuncData, ScDocument* pDoc) : + pStr( nullptr ), + mpFuncData(pFuncData), + nHandle( nHandleP ), + meType(pFuncData->GetAsyncType()), + bValid( false ) +{ + pDocs.reset(new ScAddInDocs); + pDocs->insert( pDoc ); + theAddInAsyncTbl.emplace( this ); +} + +ScAddInAsync::~ScAddInAsync() +{ + // in dTor because of theAddInAsyncTbl.DeleteAndDestroy in ScGlobal::Clear + mpFuncData->Unadvice( static_cast<double>(nHandle) ); + if ( meType == ParamType::PTR_STRING && pStr ) // include type comparison because of union + delete pStr; + pDocs.reset(); +} + +ScAddInAsync* ScAddInAsync::Get( sal_uLong nHandleP ) +{ + ScAddInAsync* pRet = nullptr; + auto it = std::find_if( + theAddInAsyncTbl.begin(), theAddInAsyncTbl.end(), + [nHandleP](std::unique_ptr<ScAddInAsync> const & el) + { return el->nHandle == nHandleP; }); + if ( it != theAddInAsyncTbl.end() ) + pRet = it->get(); + return pRet; +} + +void ScAddInAsync::CallBack( sal_uLong nHandleP, void* pData ) +{ + auto asyncIt = std::find_if( + theAddInAsyncTbl.begin(), theAddInAsyncTbl.end(), + [nHandleP](std::unique_ptr<ScAddInAsync> const & el) + { return el->nHandle == nHandleP; }); + if ( asyncIt == theAddInAsyncTbl.end() ) + return; + ScAddInAsync* p = asyncIt->get(); + + if ( !p->HasListeners() ) + { + // not in dTor because of theAddInAsyncTbl.DeleteAndDestroy in ScGlobal::Clear + theAddInAsyncTbl.erase( asyncIt ); + return ; + } + switch ( p->meType ) + { + case ParamType::PTR_DOUBLE : + p->nVal = *static_cast<double*>(pData); + break; + case ParamType::PTR_STRING : + { + char* pChar = static_cast<char*>(pData); + if ( p->pStr ) + *p->pStr = OUString( pChar, strlen(pChar),osl_getThreadTextEncoding() ); + else + p->pStr = new OUString( pChar, strlen(pChar), osl_getThreadTextEncoding() ); + break; + } + default : + OSL_FAIL( "unknown AsyncType" ); + return; + } + p->bValid = true; + p->Broadcast( ScHint(SfxHintId::ScDataChanged, ScAddress()) ); + + for ( ScDocument* pDoc : *p->pDocs ) + { + pDoc->TrackFormulas(); + pDoc->GetDocumentShell()->Broadcast( SfxHint( SfxHintId::ScDataChanged ) ); + } +} + +void ScAddInAsync::RemoveDocument( ScDocument* pDocumentP ) +{ + for( ScAddInAsyncs::reverse_iterator iter1 = theAddInAsyncTbl.rbegin(); iter1 != theAddInAsyncTbl.rend(); ++iter1 ) + { // backwards because of pointer-movement in array + ScAddInAsync* pAsync = iter1->get(); + ScAddInDocs* p = pAsync->pDocs.get(); + ScAddInDocs::iterator iter2 = p->find( pDocumentP ); + if( iter2 != p->end() ) + { + p->erase( iter2 ); + if ( p->empty() ) + { // this AddIn is not used anymore + theAddInAsyncTbl.erase( --(iter1.base()) ); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/appoptio.cxx b/sc/source/core/tool/appoptio.cxx new file mode 100644 index 0000000000..516c1a67e9 --- /dev/null +++ b/sc/source/core/tool/appoptio.cxx @@ -0,0 +1,668 @@ +/* -*- 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 <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <appoptio.hxx> +#include <global.hxx> +#include <userlist.hxx> +#include <formula/compiler.hxx> +#include <miscuno.hxx> +#include <vector> +#include <osl/diagnose.h> + +using namespace utl; +using namespace com::sun::star::uno; + +// ScAppOptions - Application Options + +ScAppOptions::ScAppOptions() +{ + SetDefaults(); +} + +ScAppOptions::ScAppOptions( const ScAppOptions& rCpy ) +{ + *this = rCpy; +} + +ScAppOptions::~ScAppOptions() +{ +} + +void ScAppOptions::SetDefaults() +{ + if ( ScOptionsUtil::IsMetricSystem() ) + eMetric = FieldUnit::CM; // default for countries with metric system + else + eMetric = FieldUnit::INCH; // default for others + + nZoom = 100; + eZoomType = SvxZoomType::PERCENT; + bSynchronizeZoom = true; + nStatusFunc = ( 1 << SUBTOTAL_FUNC_SUM ); + bAutoComplete = true; + bDetectiveAuto = true; + + pLRUList.reset( new sal_uInt16[5] ); // sensible initialization + pLRUList[0] = SC_OPCODE_SUM; + pLRUList[1] = SC_OPCODE_AVERAGE; + pLRUList[2] = SC_OPCODE_MIN; + pLRUList[3] = SC_OPCODE_MAX; + pLRUList[4] = SC_OPCODE_IF; + nLRUFuncCount = 5; + + nTrackContentColor = COL_TRANSPARENT; + nTrackInsertColor = COL_TRANSPARENT; + nTrackDeleteColor = COL_TRANSPARENT; + nTrackMoveColor = COL_TRANSPARENT; + eLinkMode = LM_ON_DEMAND; + + nDefaultObjectSizeWidth = 8000; + nDefaultObjectSizeHeight = 5000; + + mbShowSharedDocumentWarning = true; + + meKeyBindingType = ScOptionsUtil::KEY_DEFAULT; + mbLinksInsertedLikeMSExcel = false; +} + +ScAppOptions& ScAppOptions::operator=( const ScAppOptions& rCpy ) +{ + eMetric = rCpy.eMetric; + eZoomType = rCpy.eZoomType; + bSynchronizeZoom = rCpy.bSynchronizeZoom; + nZoom = rCpy.nZoom; + SetLRUFuncList( rCpy.pLRUList.get(), rCpy.nLRUFuncCount ); + nStatusFunc = rCpy.nStatusFunc; + bAutoComplete = rCpy.bAutoComplete; + bDetectiveAuto = rCpy.bDetectiveAuto; + nTrackContentColor = rCpy.nTrackContentColor; + nTrackInsertColor = rCpy.nTrackInsertColor; + nTrackDeleteColor = rCpy.nTrackDeleteColor; + nTrackMoveColor = rCpy.nTrackMoveColor; + eLinkMode = rCpy.eLinkMode; + nDefaultObjectSizeWidth = rCpy.nDefaultObjectSizeWidth; + nDefaultObjectSizeHeight = rCpy.nDefaultObjectSizeHeight; + mbShowSharedDocumentWarning = rCpy.mbShowSharedDocumentWarning; + meKeyBindingType = rCpy.meKeyBindingType; + mbLinksInsertedLikeMSExcel = rCpy.mbLinksInsertedLikeMSExcel; + return *this; +} + +void ScAppOptions::SetLRUFuncList( const sal_uInt16* pList, const sal_uInt16 nCount ) +{ + nLRUFuncCount = nCount; + + if ( nLRUFuncCount > 0 ) + { + pLRUList.reset( new sal_uInt16[nLRUFuncCount] ); + + for ( sal_uInt16 i=0; i<nLRUFuncCount; i++ ) + pLRUList[i] = pList[i]; + } + else + pLRUList.reset(); +} + +// Config Item containing app options + +static void lcl_GetLastFunctions( Any& rDest, const ScAppOptions& rOpt ) +{ + tools::Long nCount = rOpt.GetLRUFuncListCount(); + sal_uInt16* pUShorts = rOpt.GetLRUFuncList(); + if ( nCount && pUShorts ) + { + Sequence<sal_Int32> aSeq( nCount ); + sal_Int32* pArray = aSeq.getArray(); + for (tools::Long i=0; i<nCount; i++) + pArray[i] = pUShorts[i]; + rDest <<= aSeq; + } + else + rDest <<= Sequence<sal_Int32>(0); // empty +} + +static void lcl_GetSortList( Any& rDest ) +{ + const ScUserList* pUserList = ScGlobal::GetUserList(); + if (pUserList) + { + size_t nCount = pUserList->size(); + Sequence<OUString> aSeq( nCount ); + OUString* pArray = aSeq.getArray(); + for (size_t i=0; i<nCount; ++i) + pArray[i] = (*pUserList)[sal::static_int_cast<sal_uInt16>(i)].GetString(); + rDest <<= aSeq; + } + else + rDest <<= Sequence<OUString>(0); // empty +} + +constexpr OUStringLiteral CFGPATH_LAYOUT = u"Office.Calc/Layout"; + +#define SCLAYOUTOPT_MEASURE 0 +#define SCLAYOUTOPT_STATUSBAR 1 +#define SCLAYOUTOPT_ZOOMVAL 2 +#define SCLAYOUTOPT_ZOOMTYPE 3 +#define SCLAYOUTOPT_SYNCZOOM 4 +#define SCLAYOUTOPT_STATUSBARMULTI 5 + +constexpr OUStringLiteral CFGPATH_INPUT = u"Office.Calc/Input"; + +#define SCINPUTOPT_LASTFUNCS 0 +#define SCINPUTOPT_AUTOINPUT 1 +#define SCINPUTOPT_DET_AUTO 2 + +constexpr OUStringLiteral CFGPATH_REVISION = u"Office.Calc/Revision/Color"; + +#define SCREVISOPT_CHANGE 0 +#define SCREVISOPT_INSERTION 1 +#define SCREVISOPT_DELETION 2 +#define SCREVISOPT_MOVEDENTRY 3 + +constexpr OUStringLiteral CFGPATH_CONTENT = u"Office.Calc/Content/Update"; + +#define SCCONTENTOPT_LINK 0 + +constexpr OUStringLiteral CFGPATH_SORTLIST = u"Office.Calc/SortList"; + +#define SCSORTLISTOPT_LIST 0 + +constexpr OUStringLiteral CFGPATH_MISC = u"Office.Calc/Misc"; + +#define SCMISCOPT_DEFOBJWIDTH 0 +#define SCMISCOPT_DEFOBJHEIGHT 1 +#define SCMISCOPT_SHOWSHAREDDOCWARN 2 + +constexpr OUStringLiteral CFGPATH_COMPAT = u"Office.Calc/Compatibility"; + +#define SCCOMPATOPT_KEY_BINDING 0 +#define SCCOMPATOPT_LINK_LIKE_MS 1 + +// Default value of Layout/Other/StatusbarMultiFunction +#define SCLAYOUTOPT_STATUSBARMULTI_DEFAULTVAL 514 +// Default value of Layout/Other/StatusbarFunction +#define SCLAYOUTOPT_STATUSBAR_DEFAULTVAL 1 +// Legacy default value of Layout/Other/StatusbarFunction +// prior to multiple statusbar functions feature addition +#define SCLAYOUTOPT_STATUSBAR_DEFAULTVAL_LEGACY 9 + +static sal_uInt32 lcl_ConvertStatusBarFuncSetToSingle( sal_uInt32 nFuncSet ) +{ + if ( !nFuncSet ) + return 0; + for ( sal_uInt32 nFunc = 1; nFunc < 32; ++nFunc ) + if ( nFuncSet & ( 1U << nFunc ) ) + return nFunc; + return 0; +} + +Sequence<OUString> ScAppCfg::GetLayoutPropertyNames() +{ + const bool bIsMetric = ScOptionsUtil::IsMetricSystem(); + + return {(bIsMetric ? OUString("Other/MeasureUnit/Metric") + : OUString("Other/MeasureUnit/NonMetric")), // SCLAYOUTOPT_MEASURE + "Other/StatusbarFunction", // SCLAYOUTOPT_STATUSBAR + "Zoom/Value", // SCLAYOUTOPT_ZOOMVAL + "Zoom/Type", // SCLAYOUTOPT_ZOOMTYPE + "Zoom/Synchronize", // SCLAYOUTOPT_SYNCZOOM + "Other/StatusbarMultiFunction"}; // SCLAYOUTOPT_STATUSBARMULTI +} + +Sequence<OUString> ScAppCfg::GetInputPropertyNames() +{ + return {"LastFunctions", // SCINPUTOPT_LASTFUNCS + "AutoInput", // SCINPUTOPT_AUTOINPUT + "DetectiveAuto"}; // SCINPUTOPT_DET_AUTO +} + +Sequence<OUString> ScAppCfg::GetRevisionPropertyNames() +{ + return {"Change", // SCREVISOPT_CHANGE + "Insertion", // SCREVISOPT_INSERTION + "Deletion", // SCREVISOPT_DELETION + "MovedEntry"}; // SCREVISOPT_MOVEDENTRY +} + +Sequence<OUString> ScAppCfg::GetContentPropertyNames() +{ + return {"Link"}; // SCCONTENTOPT_LINK +} + +Sequence<OUString> ScAppCfg::GetSortListPropertyNames() +{ + return {"List"}; // SCSORTLISTOPT_LIST +} + +Sequence<OUString> ScAppCfg::GetMiscPropertyNames() +{ + return {"DefaultObjectSize/Width", // SCMISCOPT_DEFOBJWIDTH + "DefaultObjectSize/Height", // SCMISCOPT_DEFOBJHEIGHT + "SharedDocument/ShowWarning"}; // SCMISCOPT_SHOWSHAREDDOCWARN +} + +Sequence<OUString> ScAppCfg::GetCompatPropertyNames() +{ + return {"KeyBindings/BaseGroup", // SCCOMPATOPT_KEY_BINDING + "Links" }; // SCCOMPATOPT_LINK_LIKE_MS +} + +ScAppCfg::ScAppCfg() : + aLayoutItem( CFGPATH_LAYOUT ), + aInputItem( CFGPATH_INPUT ), + aRevisionItem( CFGPATH_REVISION ), + aContentItem( CFGPATH_CONTENT ), + aSortListItem( CFGPATH_SORTLIST ), + aMiscItem( CFGPATH_MISC ), + aCompatItem( CFGPATH_COMPAT ) +{ + aLayoutItem.EnableNotification(GetLayoutPropertyNames()); + ReadLayoutCfg(); + aLayoutItem.SetCommitLink( LINK( this, ScAppCfg, LayoutCommitHdl ) ); + aLayoutItem.SetNotifyLink( LINK( this, ScAppCfg, LayoutNotifyHdl ) ); + + aInputItem.EnableNotification(GetInputPropertyNames()); + ReadInputCfg(); + aInputItem.SetCommitLink( LINK( this, ScAppCfg, InputCommitHdl ) ); + aInputItem.SetNotifyLink( LINK( this, ScAppCfg, InputNotifyHdl ) ); + + aRevisionItem.EnableNotification(GetRevisionPropertyNames()); + ReadRevisionCfg(); + aRevisionItem.SetCommitLink( LINK( this, ScAppCfg, RevisionCommitHdl ) ); + aRevisionItem.SetNotifyLink( LINK( this, ScAppCfg, RevisionNotifyHdl ) ); + + aContentItem.EnableNotification(GetContentPropertyNames()); + ReadContentCfg(); + aContentItem.SetCommitLink( LINK( this, ScAppCfg, ContentCommitHdl ) ); + aContentItem.SetNotifyLink( LINK( this, ScAppCfg, ContentNotifyHdl ) ); + + aSortListItem.EnableNotification(GetSortListPropertyNames()); + ReadSortListCfg(); + aSortListItem.SetCommitLink( LINK( this, ScAppCfg, SortListCommitHdl ) ); + aSortListItem.SetNotifyLink( LINK( this, ScAppCfg, SortListNotifyHdl ) ); + + aMiscItem.EnableNotification(GetMiscPropertyNames()); + ReadMiscCfg(); + aMiscItem.SetCommitLink( LINK( this, ScAppCfg, MiscCommitHdl ) ); + aMiscItem.SetNotifyLink( LINK( this, ScAppCfg, MiscNotifyHdl ) ); + + aCompatItem.EnableNotification(GetCompatPropertyNames()); + ReadCompatCfg(); + aCompatItem.SetCommitLink( LINK(this, ScAppCfg, CompatCommitHdl) ); + aCompatItem.SetNotifyLink( LINK(this, ScAppCfg, CompatNotifyHdl) ); +} + +void ScAppCfg::ReadLayoutCfg() +{ + const Sequence<OUString> aNames = GetLayoutPropertyNames(); + const Sequence<Any> aValues = aLayoutItem.GetProperties(aNames); + OSL_ENSURE(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if (aValues.getLength() != aNames.getLength()) + return; + + sal_uInt32 nStatusBarFuncSingle = 0; + sal_uInt32 nStatusBarFuncMulti = 0; + + if (sal_Int32 nIntVal; aValues[SCLAYOUTOPT_MEASURE] >>= nIntVal) + SetAppMetric(static_cast<FieldUnit>(nIntVal)); + if (sal_uInt32 nUIntVal; aValues[SCLAYOUTOPT_STATUSBAR] >>= nUIntVal) + nStatusBarFuncSingle = nUIntVal; + if (sal_uInt32 nUIntVal; aValues[SCLAYOUTOPT_STATUSBARMULTI] >>= nUIntVal) + nStatusBarFuncMulti = nUIntVal; + if (sal_Int32 nIntVal; aValues[SCLAYOUTOPT_ZOOMVAL] >>= nIntVal) + SetZoom(static_cast<sal_uInt16>(nIntVal)); + if (sal_Int32 nIntVal; aValues[SCLAYOUTOPT_ZOOMTYPE] >>= nIntVal) + SetZoomType(static_cast<SvxZoomType>(nIntVal)); + SetSynchronizeZoom(ScUnoHelpFunctions::GetBoolFromAny(aValues[SCLAYOUTOPT_SYNCZOOM])); + + if (nStatusBarFuncMulti != SCLAYOUTOPT_STATUSBARMULTI_DEFAULTVAL) + SetStatusFunc(nStatusBarFuncMulti); + else if (nStatusBarFuncSingle != SCLAYOUTOPT_STATUSBAR_DEFAULTVAL + && nStatusBarFuncSingle != SCLAYOUTOPT_STATUSBAR_DEFAULTVAL_LEGACY) + { + if (nStatusBarFuncSingle) + SetStatusFunc(1 << nStatusBarFuncSingle); + else + SetStatusFunc(0); + } + else + SetStatusFunc(SCLAYOUTOPT_STATUSBARMULTI_DEFAULTVAL); +} + +void ScAppCfg::ReadInputCfg() +{ + const Sequence<OUString> aNames = GetInputPropertyNames(); + const Sequence<Any> aValues = aInputItem.GetProperties(aNames); + OSL_ENSURE(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if (aValues.getLength() != aNames.getLength()) + return; + + if (Sequence<sal_Int32> aSeq; aValues[SCINPUTOPT_LASTFUNCS] >>= aSeq) + { + sal_Int32 nCount = aSeq.getLength(); + if (nCount < SAL_MAX_UINT16) + { + std::vector<sal_uInt16> pUShorts(nCount); + for (sal_Int32 i = 0; i < nCount; i++) + pUShorts[i] = aSeq[i]; + + SetLRUFuncList(pUShorts.data(), nCount); + } + } + SetAutoComplete(ScUnoHelpFunctions::GetBoolFromAny(aValues[SCINPUTOPT_AUTOINPUT])); + SetDetectiveAuto(ScUnoHelpFunctions::GetBoolFromAny(aValues[SCINPUTOPT_DET_AUTO])); +} + +void ScAppCfg::ReadRevisionCfg() +{ + const Sequence<OUString> aNames = GetRevisionPropertyNames(); + const Sequence<Any> aValues = aRevisionItem.GetProperties(aNames); + OSL_ENSURE(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if (aValues.getLength() != aNames.getLength()) + return; + + if (sal_Int32 nIntVal; aValues[SCREVISOPT_CHANGE] >>= nIntVal) + SetTrackContentColor(Color(ColorTransparency, nIntVal)); + if (sal_Int32 nIntVal; aValues[SCREVISOPT_INSERTION] >>= nIntVal) + SetTrackInsertColor(Color(ColorTransparency, nIntVal)); + if (sal_Int32 nIntVal; aValues[SCREVISOPT_DELETION] >>= nIntVal) + SetTrackDeleteColor(Color(ColorTransparency, nIntVal)); + if (sal_Int32 nIntVal; aValues[SCREVISOPT_MOVEDENTRY] >>= nIntVal) + SetTrackMoveColor(Color(ColorTransparency, nIntVal)); +} + +void ScAppCfg::ReadContentCfg() +{ + const Sequence<OUString> aNames = GetContentPropertyNames(); + const Sequence<Any> aValues = aContentItem.GetProperties(aNames); + OSL_ENSURE(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if (aValues.getLength() != aNames.getLength()) + return; + + if (sal_Int32 nIntVal; aValues[SCCONTENTOPT_LINK] >>= nIntVal) + SetLinkMode(static_cast<ScLkUpdMode>(nIntVal)); +} + +void ScAppCfg::ReadSortListCfg() +{ + const Sequence<OUString> aNames = GetSortListPropertyNames(); + const Sequence<Any> aValues = aSortListItem.GetProperties(aNames); + OSL_ENSURE(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if (aValues.getLength() != aNames.getLength()) + return; + + if (Sequence<OUString> aSeq; aValues[SCSORTLISTOPT_LIST] >>= aSeq) + { + ScUserList aList(false); // Do not init defaults + + // if setting is "default", keep default values + //TODO: mark "default" in a safe way + const bool bDefault = (aSeq.getLength() == 1 && aSeq[0] == "NULL"); + + if (bDefault) + { + aList.AddDefaults(); + } + else + { + for (const OUString& rStr : std::as_const(aSeq)) + { + aList.emplace_back(rStr); + } + } + + ScGlobal::SetUserList(&aList); + } +} + +void ScAppCfg::ReadMiscCfg() +{ + const Sequence<OUString> aNames = GetMiscPropertyNames(); + const Sequence<Any> aValues = aMiscItem.GetProperties(aNames); + OSL_ENSURE(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if (aValues.getLength() != aNames.getLength()) + return; + + if (sal_Int32 nIntVal; aValues[SCMISCOPT_DEFOBJWIDTH] >>= nIntVal) + SetDefaultObjectSizeWidth(nIntVal); + if (sal_Int32 nIntVal; aValues[SCMISCOPT_DEFOBJHEIGHT] >>= nIntVal) + SetDefaultObjectSizeHeight(nIntVal); + SetShowSharedDocumentWarning( + ScUnoHelpFunctions::GetBoolFromAny(aValues[SCMISCOPT_SHOWSHAREDDOCWARN])); +} + +void ScAppCfg::ReadCompatCfg() +{ + const Sequence<OUString> aNames = GetCompatPropertyNames(); + const Sequence<Any> aValues = aCompatItem.GetProperties(aNames); + if (aValues.getLength() != aNames.getLength()) + return; + + sal_Int32 nIntVal = 0; // 0 = 'Default' + aValues[SCCOMPATOPT_KEY_BINDING] >>= nIntVal; + SetKeyBindingType(static_cast<ScOptionsUtil::KeyBindingType>(nIntVal)); + + if (aValues.getLength() > SCCOMPATOPT_LINK_LIKE_MS) + SetLinksInsertedLikeMSExcel( + ScUnoHelpFunctions::GetBoolFromAny(aValues[SCCOMPATOPT_LINK_LIKE_MS])); +} + +IMPL_LINK_NOARG(ScAppCfg, LayoutCommitHdl, ScLinkConfigItem&, void) +{ + Sequence<OUString> aNames = GetLayoutPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + switch(nProp) + { + case SCLAYOUTOPT_MEASURE: + pValues[nProp] <<= static_cast<sal_Int32>(GetAppMetric()); + break; + case SCLAYOUTOPT_STATUSBAR: + pValues[nProp] <<= lcl_ConvertStatusBarFuncSetToSingle( GetStatusFunc() ); + break; + case SCLAYOUTOPT_ZOOMVAL: + pValues[nProp] <<= static_cast<sal_Int32>(GetZoom()); + break; + case SCLAYOUTOPT_ZOOMTYPE: + pValues[nProp] <<= static_cast<sal_Int32>(GetZoomType()); + break; + case SCLAYOUTOPT_SYNCZOOM: + pValues[nProp] <<= GetSynchronizeZoom(); + break; + case SCLAYOUTOPT_STATUSBARMULTI: + pValues[nProp] <<= GetStatusFunc(); + break; + } + } + aLayoutItem.PutProperties(aNames, aValues); +} + +IMPL_LINK_NOARG(ScAppCfg, LayoutNotifyHdl, ScLinkConfigItem&, void) { ReadLayoutCfg(); } + +IMPL_LINK_NOARG(ScAppCfg, InputCommitHdl, ScLinkConfigItem&, void) +{ + Sequence<OUString> aNames = GetInputPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + switch(nProp) + { + case SCINPUTOPT_LASTFUNCS: + lcl_GetLastFunctions( pValues[nProp], *this ); + break; + case SCINPUTOPT_AUTOINPUT: + pValues[nProp] <<= GetAutoComplete(); + break; + case SCINPUTOPT_DET_AUTO: + pValues[nProp] <<= GetDetectiveAuto(); + break; + } + } + aInputItem.PutProperties(aNames, aValues); +} + +IMPL_LINK_NOARG(ScAppCfg, InputNotifyHdl, ScLinkConfigItem&, void) { ReadInputCfg(); } + +IMPL_LINK_NOARG(ScAppCfg, RevisionCommitHdl, ScLinkConfigItem&, void) +{ + Sequence<OUString> aNames = GetRevisionPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + switch(nProp) + { + case SCREVISOPT_CHANGE: + pValues[nProp] <<= GetTrackContentColor(); + break; + case SCREVISOPT_INSERTION: + pValues[nProp] <<= GetTrackInsertColor(); + break; + case SCREVISOPT_DELETION: + pValues[nProp] <<= GetTrackDeleteColor(); + break; + case SCREVISOPT_MOVEDENTRY: + pValues[nProp] <<= GetTrackMoveColor(); + break; + } + } + aRevisionItem.PutProperties(aNames, aValues); +} + +IMPL_LINK_NOARG(ScAppCfg, RevisionNotifyHdl, ScLinkConfigItem&, void) { ReadRevisionCfg(); } + +IMPL_LINK_NOARG(ScAppCfg, ContentCommitHdl, ScLinkConfigItem&, void) +{ + Sequence<OUString> aNames = GetContentPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + switch(nProp) + { + case SCCONTENTOPT_LINK: + pValues[nProp] <<= static_cast<sal_Int32>(GetLinkMode()); + break; + } + } + aContentItem.PutProperties(aNames, aValues); +} + +IMPL_LINK_NOARG(ScAppCfg, ContentNotifyHdl, ScLinkConfigItem&, void) { ReadContentCfg(); } + +IMPL_LINK_NOARG(ScAppCfg, SortListCommitHdl, ScLinkConfigItem&, void) +{ + Sequence<OUString> aNames = GetSortListPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + switch(nProp) + { + case SCSORTLISTOPT_LIST: + lcl_GetSortList( pValues[nProp] ); + break; + } + } + aSortListItem.PutProperties(aNames, aValues); +} + +IMPL_LINK_NOARG(ScAppCfg, SortListNotifyHdl, ScLinkConfigItem&, void) { ReadSortListCfg(); } + +IMPL_LINK_NOARG(ScAppCfg, MiscCommitHdl, ScLinkConfigItem&, void) +{ + Sequence<OUString> aNames = GetMiscPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + switch(nProp) + { + case SCMISCOPT_DEFOBJWIDTH: + pValues[nProp] <<= GetDefaultObjectSizeWidth(); + break; + case SCMISCOPT_DEFOBJHEIGHT: + pValues[nProp] <<= GetDefaultObjectSizeHeight(); + break; + case SCMISCOPT_SHOWSHAREDDOCWARN: + pValues[nProp] <<= GetShowSharedDocumentWarning(); + break; + } + } + aMiscItem.PutProperties(aNames, aValues); +} + +IMPL_LINK_NOARG(ScAppCfg, MiscNotifyHdl, ScLinkConfigItem&, void) { ReadMiscCfg(); } + +IMPL_LINK_NOARG(ScAppCfg, CompatCommitHdl, ScLinkConfigItem&, void) +{ + Sequence<OUString> aNames = GetCompatPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + for (int nProp = 0; nProp < aNames.getLength(); ++nProp) + { + switch(nProp) + { + case SCCOMPATOPT_KEY_BINDING: + pValues[nProp] <<= static_cast<sal_Int32>(GetKeyBindingType()); + break; + case SCCOMPATOPT_LINK_LIKE_MS: + pValues[nProp] <<= GetLinksInsertedLikeMSExcel(); + break; + } + } + aCompatItem.PutProperties(aNames, aValues); +} + +IMPL_LINK_NOARG(ScAppCfg, CompatNotifyHdl, ScLinkConfigItem&, void) { ReadCompatCfg(); } + +void ScAppCfg::SetOptions( const ScAppOptions& rNew ) +{ + *static_cast<ScAppOptions*>(this) = rNew; + + aLayoutItem.SetModified(); + aInputItem.SetModified(); + aRevisionItem.SetModified(); + aContentItem.SetModified(); + aSortListItem.SetModified(); + aMiscItem.SetModified(); + aCompatItem.SetModified(); + + aLayoutItem.Commit(); + aInputItem.Commit(); + aRevisionItem.Commit(); + aContentItem.Commit(); + aSortListItem.Commit(); + aMiscItem.Commit(); + aCompatItem.Commit(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/arraysumSSE2.cxx b/sc/source/core/tool/arraysumSSE2.cxx new file mode 100644 index 0000000000..f7b87070b7 --- /dev/null +++ b/sc/source/core/tool/arraysumSSE2.cxx @@ -0,0 +1,120 @@ +/* -*- 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/. + * + */ + +#include <arraysumfunctor.hxx> + +#include <tools/simdsupport.hxx> + +#include <stdlib.h> + +#if SC_USE_SSE2 + +namespace sc::op +{ +/** Kahan sum with SSE2. + */ +static inline void sumSSE2(__m128d& sum, __m128d& err, const __m128d& value) +{ + const __m128d ANNULATE_SIGN_BIT = _mm_castsi128_pd(_mm_set1_epi64x(0x7FFF'FFFF'FFFF'FFFF)); + // Temporal parameter + __m128d t = _mm_add_pd(sum, value); + // Absolute value of the total sum + __m128d asum = _mm_and_pd(sum, ANNULATE_SIGN_BIT); + // Absolute value of the value to add + __m128d avalue = _mm_and_pd(value, ANNULATE_SIGN_BIT); + // Compare the absolute values sum >= value + __m128d mask = _mm_cmpge_pd(asum, avalue); + // The following code has this form ( a - t + b) + // Case 1: a = sum b = value + // Case 2: a = value b = sum + __m128d a = _mm_add_pd(_mm_and_pd(mask, sum), _mm_andnot_pd(mask, value)); + __m128d b = _mm_add_pd(_mm_and_pd(mask, value), _mm_andnot_pd(mask, sum)); + err = _mm_add_pd(err, _mm_add_pd(_mm_sub_pd(a, t), b)); + // Store result + sum = t; +} + +/** Execute Kahan sum with SSE2. + */ +KahanSum executeSSE2(size_t& i, size_t nSize, const double* pCurrent) +{ + // Make sure we don't fall out of bounds. + // This works by sums of 8 terms. + // So the 8'th term is i+7 + // If we iterate until nSize won't fall out of bounds + if (nSize > i + 7) + { + // Setup sums and errors as 0 + __m128d sum1 = _mm_setzero_pd(); + __m128d err1 = _mm_setzero_pd(); + __m128d sum2 = _mm_setzero_pd(); + __m128d err2 = _mm_setzero_pd(); + __m128d sum3 = _mm_setzero_pd(); + __m128d err3 = _mm_setzero_pd(); + __m128d sum4 = _mm_setzero_pd(); + __m128d err4 = _mm_setzero_pd(); + + for (; i + 7 < nSize; i += 8) + { + // Kahan sum 1 + __m128d load1 = _mm_loadu_pd(pCurrent); + sumSSE2(sum1, err1, load1); + pCurrent += 2; + + // Kahan sum 2 + __m128d load2 = _mm_loadu_pd(pCurrent); + sumSSE2(sum2, err2, load2); + pCurrent += 2; + + // Kahan sum 3 + __m128d load3 = _mm_loadu_pd(pCurrent); + sumSSE2(sum3, err3, load3); + pCurrent += 2; + + // Kahan sum 4 + __m128d load4 = _mm_loadu_pd(pCurrent); + sumSSE2(sum4, err4, load4); + pCurrent += 2; + } + + // Now we combine pairwise summation with Kahan summation + + // 1+2 3+4 -> 1, 3 + sumSSE2(sum1, err1, sum2); + sumSSE2(sum1, err1, err2); + sumSSE2(sum3, err3, sum4); + sumSSE2(sum3, err3, err4); + + // 1+3 -> 1 + sumSSE2(sum1, err1, sum3); + sumSSE2(sum1, err1, err3); + + // Store results + double sums[2]; + double errs[2]; + _mm_storeu_pd(&sums[0], sum1); + _mm_storeu_pd(&errs[0], err1); + + // First Kahan & pairwise summation + // 0+1 -> 0 + KahanSum::sumNeumaierNormal(sums[0], errs[0], sums[1]); + KahanSum::sumNeumaierNormal(sums[0], errs[0], errs[1]); + + // Store result + return { sums[0], errs[0] }; + } + return { 0.0, 0.0 }; +} + +} // namespace + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/autoform.cxx b/sc/source/core/tool/autoform.cxx new file mode 100644 index 0000000000..d6a178bf18 --- /dev/null +++ b/sc/source/core/tool/autoform.cxx @@ -0,0 +1,934 @@ +/* -*- 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 <memory> +#include <autoform.hxx> + +#include <sal/log.hxx> +#include <sfx2/docfile.hxx> +#include <unotools/pathoptions.hxx> +#include <svl/intitem.hxx> +#include <svl/itemset.hxx> +#include <vcl/outdev.hxx> +#include <svx/algitem.hxx> +#include <svx/dialmgr.hxx> +#include <svx/rotmodit.hxx> +#include <svx/strings.hrc> +#include <editeng/adjustitem.hxx> +#include <editeng/borderline.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/justifyitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/lineitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/wghtitem.hxx> +#include <tools/urlobj.hxx> +#include <comphelper/fileformat.h> +#include <unotools/collatorwrapper.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <tools/tenccvt.hxx> +#include <osl/diagnose.h> +#include <osl/thread.hxx> + +#include <attrib.hxx> +#include <globstr.hrc> +#include <scitems.hxx> +#include <scresid.hxx> +#include <document.hxx> + +/* + * XXX: BIG RED NOTICE! Changes MUST be binary file format compatible and MUST + * be synchronized with Writer's SwTableAutoFmtTbl sw/source/core/doc/tblafmt.cxx + */ + +constexpr OUString sAutoTblFmtName = u"autotbl.fmt"_ustr; + +// till SO5PF +const sal_uInt16 AUTOFORMAT_ID_X = 9501; +const sal_uInt16 AUTOFORMAT_ID_358 = 9601; +const sal_uInt16 AUTOFORMAT_DATA_ID_X = 9502; + +// from SO5 on +// in following versions the value of the IDs must be higher +const sal_uInt16 AUTOFORMAT_ID_504 = 9801; +const sal_uInt16 AUTOFORMAT_DATA_ID_504 = 9802; + +const sal_uInt16 AUTOFORMAT_DATA_ID_552 = 9902; + +// --- from 680/dr25 on: store strings as UTF-8 +const sal_uInt16 AUTOFORMAT_ID_680DR25 = 10021; + +// --- Bug fix to fdo#31005: Table Autoformats does not save/apply all properties (Writer and Calc) +const sal_uInt16 AUTOFORMAT_ID_31005 = 10041; +const sal_uInt16 AUTOFORMAT_DATA_ID_31005 = 10042; + +// current version +const sal_uInt16 AUTOFORMAT_ID = AUTOFORMAT_ID_31005; +const sal_uInt16 AUTOFORMAT_DATA_ID = AUTOFORMAT_DATA_ID_31005; + +namespace +{ + /// Read an AutoFormatSwBlob from stream. + SvStream& operator>>(SvStream &stream, AutoFormatSwBlob &blob) + { + blob.Reset(); + + sal_uInt64 endOfBlob = 0; + stream.ReadUInt64( endOfBlob ); + + const sal_uInt64 currentPosition = stream.Tell(); + const sal_uInt64 blobSize = endOfBlob - currentPosition; + // A zero-size indicates an empty blob. This happens when Calc creates a new autoformat, + // since it (naturally) doesn't have any writer-specific data to write. + if (blobSize) + { + blob.pData.reset(new sal_uInt8[blobSize]); + blob.size = static_cast<std::size_t>(blobSize); + stream.ReadBytes(blob.pData.get(), blob.size); + } + + return stream; + } + + /// Write an AutoFormatSwBlob to stream. + SvStream& WriteAutoFormatSwBlob(SvStream &stream, const AutoFormatSwBlob &blob) + { + const sal_uInt64 endOfBlob = stream.Tell() + sizeof(sal_uInt64) + blob.size; + stream.WriteUInt64( endOfBlob ); + if (blob.size) + stream.WriteBytes(blob.pData.get(), blob.size); + + return stream; + } +} + +ScAfVersions::ScAfVersions() +{ +} + +void ScAfVersions::Load( SvStream& rStream, sal_uInt16 nVer ) +{ + LoadBlockA(rStream, nVer); + if (nVer >= AUTOFORMAT_ID_31005) + { + rStream >> swVersions; + } + LoadBlockB(rStream, nVer); +} + +void ScAfVersions::Write(SvStream& rStream, sal_uInt16 fileVersion) +{ + AutoFormatVersions::WriteBlockA(rStream, fileVersion); + + if (fileVersion >= SOFFICE_FILEFORMAT_50) + { + WriteAutoFormatSwBlob( rStream, swVersions ); + } + + AutoFormatVersions::WriteBlockB(rStream, fileVersion); +} + +ScAutoFormatDataField::ScAutoFormatDataField() +{ + // need to set default instances for base class AutoFormatBase here + // due to resource defines (e.g. ATTR_FONT) which are not available + // in svx and different in the different usages of derivations + m_aFont = std::make_unique<SvxFontItem>(ATTR_FONT); + m_aHeight = std::make_unique<SvxFontHeightItem>(240, 100, ATTR_FONT_HEIGHT); + m_aWeight = std::make_unique<SvxWeightItem>(WEIGHT_NORMAL, ATTR_FONT_WEIGHT); + m_aPosture = std::make_unique<SvxPostureItem>(ITALIC_NONE, ATTR_FONT_POSTURE); + m_aCJKFont = std::make_unique<SvxFontItem>(ATTR_CJK_FONT); + m_aCJKHeight = std::make_unique<SvxFontHeightItem>(240, 100, ATTR_CJK_FONT_HEIGHT); + m_aCJKWeight = std::make_unique<SvxWeightItem>(WEIGHT_NORMAL, ATTR_CJK_FONT_WEIGHT); + m_aCJKPosture = std::make_unique<SvxPostureItem>(ITALIC_NONE, ATTR_CJK_FONT_POSTURE); + m_aCTLFont = std::make_unique<SvxFontItem>(ATTR_CTL_FONT); + m_aCTLHeight = std::make_unique<SvxFontHeightItem>(240, 100, ATTR_CTL_FONT_HEIGHT); + m_aCTLWeight = std::make_unique<SvxWeightItem>(WEIGHT_NORMAL, ATTR_CTL_FONT_WEIGHT); + m_aCTLPosture = std::make_unique<SvxPostureItem>(ITALIC_NONE, ATTR_CTL_FONT_POSTURE); + m_aUnderline = std::make_unique<SvxUnderlineItem>(LINESTYLE_NONE,ATTR_FONT_UNDERLINE); + m_aOverline = std::make_unique<SvxOverlineItem>(LINESTYLE_NONE,ATTR_FONT_OVERLINE); + m_aCrossedOut = std::make_unique<SvxCrossedOutItem>(STRIKEOUT_NONE, ATTR_FONT_CROSSEDOUT); + m_aContour = std::make_unique<SvxContourItem>(false, ATTR_FONT_CONTOUR); + m_aShadowed = std::make_unique<SvxShadowedItem>(false, ATTR_FONT_SHADOWED); + m_aColor = std::make_unique<SvxColorItem>(ATTR_FONT_COLOR); + m_aBox = std::make_unique<SvxBoxItem>(ATTR_BORDER); + m_aTLBR = std::make_unique<SvxLineItem>(ATTR_BORDER_TLBR); + m_aBLTR = std::make_unique<SvxLineItem>(ATTR_BORDER_BLTR); + m_aBackground = std::make_unique<SvxBrushItem>(ATTR_BACKGROUND); + m_aAdjust = std::make_unique<SvxAdjustItem>(SvxAdjust::Left, 0); + m_aHorJustify = std::make_unique<SvxHorJustifyItem>(SvxCellHorJustify::Standard, ATTR_HOR_JUSTIFY); + m_aVerJustify = std::make_unique<SvxVerJustifyItem>(SvxCellVerJustify::Standard, ATTR_VER_JUSTIFY); + m_aStacked = std::make_unique<ScVerticalStackCell>(); + m_aMargin = std::make_unique<SvxMarginItem>(ATTR_MARGIN); + m_aLinebreak = std::make_unique<ScLineBreakCell>(); + m_aRotateAngle = std::make_unique<ScRotateValueItem>(0_deg100); + m_aRotateMode = std::make_unique<SvxRotateModeItem>(SVX_ROTATE_MODE_STANDARD, ATTR_ROTATE_MODE); +} + +ScAutoFormatDataField::ScAutoFormatDataField( const ScAutoFormatDataField& rCopy ) +: AutoFormatBase(rCopy), + // m_swFields was not copied in original, needed? + aNumFormat( rCopy.aNumFormat ) +{ +} + +ScAutoFormatDataField::~ScAutoFormatDataField() +{ +} + +bool ScAutoFormatDataField::Load( SvStream& rStream, const ScAfVersions& rVersions, sal_uInt16 nVer ) +{ + LoadBlockA( rStream, rVersions, nVer ); + + if (nVer >= AUTOFORMAT_DATA_ID_31005) + { + rStream >> m_swFields; + } + + LoadBlockB( rStream, rVersions, nVer ); + + if( 0 == rVersions.nNumFormatVersion ) + { + // --- from 680/dr25 on: store strings as UTF-8 + rtl_TextEncoding eCharSet = (nVer >= AUTOFORMAT_ID_680DR25) ? RTL_TEXTENCODING_UTF8 : rStream.GetStreamCharSet(); + aNumFormat.Load( rStream, eCharSet ); + } + + // adjust charset in font + rtl_TextEncoding eSysSet = osl_getThreadTextEncoding(); + rtl_TextEncoding eSrcSet = rStream.GetStreamCharSet(); + if( eSrcSet != eSysSet && m_aFont->GetCharSet() == eSrcSet ) + m_aFont->SetCharSet(eSysSet); + + return (rStream.GetError() == ERRCODE_NONE); +} + +bool ScAutoFormatDataField::Save( SvStream& rStream, sal_uInt16 fileVersion ) +{ + SaveBlockA( rStream, fileVersion ); + + if (fileVersion >= SOFFICE_FILEFORMAT_50) + { + WriteAutoFormatSwBlob( rStream, m_swFields ); + } + + SaveBlockB( rStream, fileVersion ); + + // --- from 680/dr25 on: store strings as UTF-8 + aNumFormat.Save( rStream, RTL_TEXTENCODING_UTF8 ); + + return (rStream.GetError() == ERRCODE_NONE); +} + +ScAutoFormatData::ScAutoFormatData() +{ + nStrResId = USHRT_MAX; + + bIncludeValueFormat = + bIncludeFont = + bIncludeJustify = + bIncludeFrame = + bIncludeBackground = + bIncludeWidthHeight = true; + + for( sal_uInt16 nIndex = 0; nIndex < 16; ++nIndex ) + ppDataField[ nIndex ].reset( new ScAutoFormatDataField ); +} + +ScAutoFormatData::ScAutoFormatData( const ScAutoFormatData& rData ) : + aName( rData.aName ), + nStrResId( rData.nStrResId ), + bIncludeFont( rData.bIncludeFont ), + bIncludeJustify( rData.bIncludeJustify ), + bIncludeFrame( rData.bIncludeFrame ), + bIncludeBackground( rData.bIncludeBackground ), + bIncludeValueFormat( rData.bIncludeValueFormat ), + bIncludeWidthHeight( rData.bIncludeWidthHeight ) +{ + for( sal_uInt16 nIndex = 0; nIndex < 16; ++nIndex ) + ppDataField[ nIndex ].reset( new ScAutoFormatDataField( rData.GetField( nIndex ) ) ); +} + +ScAutoFormatData::~ScAutoFormatData() +{ +} + +ScAutoFormatDataField& ScAutoFormatData::GetField( sal_uInt16 nIndex ) +{ + OSL_ENSURE( nIndex < 16, "ScAutoFormatData::GetField - illegal index" ); + OSL_ENSURE( ppDataField[ nIndex ], "ScAutoFormatData::GetField - no data" ); + return *ppDataField[ nIndex ]; +} + +const ScAutoFormatDataField& ScAutoFormatData::GetField( sal_uInt16 nIndex ) const +{ + OSL_ENSURE( nIndex < 16, "ScAutoFormatData::GetField - illegal index" ); + OSL_ENSURE( ppDataField[ nIndex ], "ScAutoFormatData::GetField - no data" ); + return *ppDataField[ nIndex ]; +} + +const SfxPoolItem* ScAutoFormatData::GetItem( sal_uInt16 nIndex, sal_uInt16 nWhich ) const +{ + const ScAutoFormatDataField& rField = GetField( nIndex ); + switch( nWhich ) + { + case ATTR_FONT: return &rField.GetFont(); + case ATTR_FONT_HEIGHT: return &rField.GetHeight(); + case ATTR_FONT_WEIGHT: return &rField.GetWeight(); + case ATTR_FONT_POSTURE: return &rField.GetPosture(); + case ATTR_CJK_FONT: return &rField.GetCJKFont(); + case ATTR_CJK_FONT_HEIGHT: return &rField.GetCJKHeight(); + case ATTR_CJK_FONT_WEIGHT: return &rField.GetCJKWeight(); + case ATTR_CJK_FONT_POSTURE: return &rField.GetCJKPosture(); + case ATTR_CTL_FONT: return &rField.GetCTLFont(); + case ATTR_CTL_FONT_HEIGHT: return &rField.GetCTLHeight(); + case ATTR_CTL_FONT_WEIGHT: return &rField.GetCTLWeight(); + case ATTR_CTL_FONT_POSTURE: return &rField.GetCTLPosture(); + case ATTR_FONT_UNDERLINE: return &rField.GetUnderline(); + case ATTR_FONT_OVERLINE: return &rField.GetOverline(); + case ATTR_FONT_CROSSEDOUT: return &rField.GetCrossedOut(); + case ATTR_FONT_CONTOUR: return &rField.GetContour(); + case ATTR_FONT_SHADOWED: return &rField.GetShadowed(); + case ATTR_FONT_COLOR: return &rField.GetColor(); + case ATTR_BORDER: return &rField.GetBox(); + case ATTR_BORDER_TLBR: return &rField.GetTLBR(); + case ATTR_BORDER_BLTR: return &rField.GetBLTR(); + case ATTR_BACKGROUND: return &rField.GetBackground(); + case ATTR_HOR_JUSTIFY: return &rField.GetHorJustify(); + case ATTR_VER_JUSTIFY: return &rField.GetVerJustify(); + case ATTR_STACKED: return &rField.GetStacked(); + case ATTR_MARGIN: return &rField.GetMargin(); + case ATTR_LINEBREAK: return &rField.GetLinebreak(); + case ATTR_ROTATE_VALUE: return &rField.GetRotateAngle(); + case ATTR_ROTATE_MODE: return &rField.GetRotateMode(); + } + return nullptr; +} + +void ScAutoFormatData::PutItem( sal_uInt16 nIndex, const SfxPoolItem& rItem ) +{ + ScAutoFormatDataField& rField = GetField( nIndex ); + switch( rItem.Which() ) + { + case ATTR_FONT: rField.SetFont( rItem.StaticWhichCast(ATTR_FONT) ); break; + case ATTR_FONT_HEIGHT: rField.SetHeight( rItem.StaticWhichCast(ATTR_FONT_HEIGHT) ); break; + case ATTR_FONT_WEIGHT: rField.SetWeight( rItem.StaticWhichCast(ATTR_FONT_WEIGHT) ); break; + case ATTR_FONT_POSTURE: rField.SetPosture( rItem.StaticWhichCast(ATTR_FONT_POSTURE) ); break; + case ATTR_CJK_FONT: rField.SetCJKFont( rItem.StaticWhichCast(ATTR_CJK_FONT) ); break; + case ATTR_CJK_FONT_HEIGHT: rField.SetCJKHeight( rItem.StaticWhichCast(ATTR_CJK_FONT_HEIGHT) ); break; + case ATTR_CJK_FONT_WEIGHT: rField.SetCJKWeight( rItem.StaticWhichCast(ATTR_CJK_FONT_WEIGHT) ); break; + case ATTR_CJK_FONT_POSTURE: rField.SetCJKPosture( rItem.StaticWhichCast(ATTR_CJK_FONT_POSTURE) ); break; + case ATTR_CTL_FONT: rField.SetCTLFont( rItem.StaticWhichCast(ATTR_CTL_FONT) ); break; + case ATTR_CTL_FONT_HEIGHT: rField.SetCTLHeight( rItem.StaticWhichCast(ATTR_CTL_FONT_HEIGHT) ); break; + case ATTR_CTL_FONT_WEIGHT: rField.SetCTLWeight( rItem.StaticWhichCast(ATTR_CTL_FONT_WEIGHT) ); break; + case ATTR_CTL_FONT_POSTURE: rField.SetCTLPosture( rItem.StaticWhichCast(ATTR_CTL_FONT_POSTURE) ); break; + case ATTR_FONT_UNDERLINE: rField.SetUnderline( rItem.StaticWhichCast(ATTR_FONT_UNDERLINE) ); break; + case ATTR_FONT_OVERLINE: rField.SetOverline( rItem.StaticWhichCast(ATTR_FONT_OVERLINE) ); break; + case ATTR_FONT_CROSSEDOUT: rField.SetCrossedOut( rItem.StaticWhichCast(ATTR_FONT_CROSSEDOUT) ); break; + case ATTR_FONT_CONTOUR: rField.SetContour( rItem.StaticWhichCast(ATTR_FONT_CONTOUR) ); break; + case ATTR_FONT_SHADOWED: rField.SetShadowed( rItem.StaticWhichCast(ATTR_FONT_SHADOWED) ); break; + case ATTR_FONT_COLOR: rField.SetColor( rItem.StaticWhichCast(ATTR_FONT_COLOR) ); break; + case ATTR_BORDER: rField.SetBox( rItem.StaticWhichCast(ATTR_BORDER) ); break; + case ATTR_BORDER_TLBR: rField.SetTLBR( rItem.StaticWhichCast(ATTR_BORDER_TLBR) ); break; + case ATTR_BORDER_BLTR: rField.SetBLTR( rItem.StaticWhichCast(ATTR_BORDER_BLTR) ); break; + case ATTR_BACKGROUND: rField.SetBackground( rItem.StaticWhichCast(ATTR_BACKGROUND) ); break; + case ATTR_HOR_JUSTIFY: rField.SetHorJustify( rItem.StaticWhichCast(ATTR_HOR_JUSTIFY) ); break; + case ATTR_VER_JUSTIFY: rField.SetVerJustify( rItem.StaticWhichCast(ATTR_VER_JUSTIFY) ); break; + case ATTR_STACKED: rField.SetStacked( rItem.StaticWhichCast(ATTR_STACKED) ); break; + case ATTR_MARGIN: rField.SetMargin( rItem.StaticWhichCast(ATTR_MARGIN) ); break; + case ATTR_LINEBREAK: rField.SetLinebreak( rItem.StaticWhichCast(ATTR_LINEBREAK) ); break; + case ATTR_ROTATE_VALUE: rField.SetRotateAngle( rItem.StaticWhichCast(ATTR_ROTATE_VALUE) ); break; + case ATTR_ROTATE_MODE: rField.SetRotateMode( rItem.StaticWhichCast(ATTR_ROTATE_MODE) ); break; + } +} + +void ScAutoFormatData::CopyItem( sal_uInt16 nToIndex, sal_uInt16 nFromIndex, sal_uInt16 nWhich ) +{ + const SfxPoolItem* pItem = GetItem( nFromIndex, nWhich ); + if( pItem ) + PutItem( nToIndex, *pItem ); +} + +const ScNumFormatAbbrev& ScAutoFormatData::GetNumFormat( sal_uInt16 nIndex ) const +{ + return GetField( nIndex ).GetNumFormat(); +} + +bool ScAutoFormatData::HasSameData( sal_uInt16 nIndex1, sal_uInt16 nIndex2 ) const +{ + bool bEqual = true; + const ScAutoFormatDataField& rField1 = GetField( nIndex1 ); + const ScAutoFormatDataField& rField2 = GetField( nIndex2 ); + + if( bIncludeValueFormat ) + { + bEqual = bEqual + && (rField1.GetNumFormat() == rField2.GetNumFormat()); + } + if( bIncludeFont ) + { + bEqual = bEqual + && (rField1.GetFont() == rField2.GetFont()) + && (rField1.GetHeight() == rField2.GetHeight()) + && (rField1.GetWeight() == rField2.GetWeight()) + && (rField1.GetPosture() == rField2.GetPosture()) + && (rField1.GetCJKFont() == rField2.GetCJKFont()) + && (rField1.GetCJKHeight() == rField2.GetCJKHeight()) + && (rField1.GetCJKWeight() == rField2.GetCJKWeight()) + && (rField1.GetCJKPosture() == rField2.GetCJKPosture()) + && (rField1.GetCTLFont() == rField2.GetCTLFont()) + && (rField1.GetCTLHeight() == rField2.GetCTLHeight()) + && (rField1.GetCTLWeight() == rField2.GetCTLWeight()) + && (rField1.GetCTLPosture() == rField2.GetCTLPosture()) + && (rField1.GetUnderline() == rField2.GetUnderline()) + && (rField1.GetOverline() == rField2.GetOverline()) + && (rField1.GetCrossedOut() == rField2.GetCrossedOut()) + && (rField1.GetContour() == rField2.GetContour()) + && (rField1.GetShadowed() == rField2.GetShadowed()) + && (rField1.GetColor() == rField2.GetColor()); + } + if( bIncludeJustify ) + { + bEqual = bEqual + && (rField1.GetHorJustify() == rField2.GetHorJustify()) + && (rField1.GetVerJustify() == rField2.GetVerJustify()) + && (rField1.GetStacked() == rField2.GetStacked()) + && (rField1.GetLinebreak() == rField2.GetLinebreak()) + && (rField1.GetMargin() == rField2.GetMargin()) + && (rField1.GetRotateAngle() == rField2.GetRotateAngle()) + && (rField1.GetRotateMode() == rField2.GetRotateMode()); + } + if( bIncludeFrame ) + { + bEqual = bEqual + && (rField1.GetBox() == rField2.GetBox()) + && (rField1.GetTLBR() == rField2.GetTLBR()) + && (rField1.GetBLTR() == rField2.GetBLTR()); + } + if( bIncludeBackground ) + { + bEqual = bEqual + && (rField1.GetBackground() == rField2.GetBackground()); + } + return bEqual; +} + +void ScAutoFormatData::FillToItemSet( sal_uInt16 nIndex, SfxItemSet& rItemSet, const ScDocument& rDoc ) const +{ + const ScAutoFormatDataField& rField = GetField( nIndex ); + + if( bIncludeValueFormat ) + { + ScNumFormatAbbrev& rNumFormat = const_cast<ScNumFormatAbbrev&>(rField.GetNumFormat()); + SfxUInt32Item aValueFormat( ATTR_VALUE_FORMAT, 0 ); + aValueFormat.SetValue( rNumFormat.GetFormatIndex( *rDoc.GetFormatTable() ) ); + rItemSet.Put( aValueFormat ); + rItemSet.Put( SvxLanguageItem( rNumFormat.GetLanguage(), ATTR_LANGUAGE_FORMAT ) ); + } + if( bIncludeFont ) + { + rItemSet.Put( rField.GetFont() ); + rItemSet.Put( rField.GetHeight() ); + rItemSet.Put( rField.GetWeight() ); + rItemSet.Put( rField.GetPosture() ); + // do not insert empty CJK font + const SvxFontItem& rCJKFont = rField.GetCJKFont(); + if (!rCJKFont.GetStyleName().isEmpty()) + { + rItemSet.Put( rCJKFont ); + rItemSet.Put( rField.GetCJKHeight() ); + rItemSet.Put( rField.GetCJKWeight() ); + rItemSet.Put( rField.GetCJKPosture() ); + } + else + { + SvxFontHeightItem aFontHeightItem(rField.GetHeight()); + aFontHeightItem.SetWhich(ATTR_CJK_FONT_HEIGHT); + rItemSet.Put( aFontHeightItem ); + SvxWeightItem aWeightItem(rField.GetWeight()); + aWeightItem.SetWhich(ATTR_CJK_FONT_WEIGHT); + rItemSet.Put( aWeightItem ); + SvxPostureItem aPostureItem(rField.GetPosture()); + aPostureItem.SetWhich(ATTR_CJK_FONT_POSTURE); + rItemSet.Put( aPostureItem ); + } + // do not insert empty CTL font + const SvxFontItem& rCTLFont = rField.GetCTLFont(); + if (!rCTLFont.GetStyleName().isEmpty()) + { + rItemSet.Put( rCTLFont ); + rItemSet.Put( rField.GetCTLHeight() ); + rItemSet.Put( rField.GetCTLWeight() ); + rItemSet.Put( rField.GetCTLPosture() ); + } + else + { + SvxFontHeightItem aFontHeightItem(rField.GetHeight()); + aFontHeightItem.SetWhich(ATTR_CTL_FONT_HEIGHT); + rItemSet.Put( aFontHeightItem ); + SvxWeightItem aWeightItem(rField.GetWeight()); + aWeightItem.SetWhich(ATTR_CTL_FONT_WEIGHT); + rItemSet.Put( aWeightItem ); + SvxPostureItem aPostureItem(rField.GetPosture()); + aPostureItem.SetWhich(ATTR_CTL_FONT_POSTURE); + rItemSet.Put( aPostureItem ); + } + rItemSet.Put( rField.GetUnderline() ); + rItemSet.Put( rField.GetOverline() ); + rItemSet.Put( rField.GetCrossedOut() ); + rItemSet.Put( rField.GetContour() ); + rItemSet.Put( rField.GetShadowed() ); + rItemSet.Put( rField.GetColor() ); + } + if( bIncludeJustify ) + { + rItemSet.Put( rField.GetHorJustify() ); + rItemSet.Put( rField.GetVerJustify() ); + rItemSet.Put( rField.GetStacked() ); + rItemSet.Put( rField.GetLinebreak() ); + rItemSet.Put( rField.GetMargin() ); + rItemSet.Put( rField.GetRotateAngle() ); + rItemSet.Put( rField.GetRotateMode() ); + } + if( bIncludeFrame ) + { + rItemSet.Put( rField.GetBox() ); + rItemSet.Put( rField.GetTLBR() ); + rItemSet.Put( rField.GetBLTR() ); + } + if( bIncludeBackground ) + rItemSet.Put( rField.GetBackground() ); +} + +void ScAutoFormatData::GetFromItemSet( sal_uInt16 nIndex, const SfxItemSet& rItemSet, const ScNumFormatAbbrev& rNumFormat ) +{ + ScAutoFormatDataField& rField = GetField( nIndex ); + + rField.SetNumFormat ( rNumFormat); + rField.SetFont ( rItemSet.Get( ATTR_FONT ) ); + rField.SetHeight ( rItemSet.Get( ATTR_FONT_HEIGHT ) ); + rField.SetWeight ( rItemSet.Get( ATTR_FONT_WEIGHT ) ); + rField.SetPosture ( rItemSet.Get( ATTR_FONT_POSTURE ) ); + rField.SetCJKFont ( rItemSet.Get( ATTR_CJK_FONT ) ); + rField.SetCJKHeight ( rItemSet.Get( ATTR_CJK_FONT_HEIGHT ) ); + rField.SetCJKWeight ( rItemSet.Get( ATTR_CJK_FONT_WEIGHT ) ); + rField.SetCJKPosture ( rItemSet.Get( ATTR_CJK_FONT_POSTURE ) ); + rField.SetCTLFont ( rItemSet.Get( ATTR_CTL_FONT ) ); + rField.SetCTLHeight ( rItemSet.Get( ATTR_CTL_FONT_HEIGHT ) ); + rField.SetCTLWeight ( rItemSet.Get( ATTR_CTL_FONT_WEIGHT ) ); + rField.SetCTLPosture ( rItemSet.Get( ATTR_CTL_FONT_POSTURE ) ); + rField.SetUnderline ( rItemSet.Get( ATTR_FONT_UNDERLINE ) ); + rField.SetOverline ( rItemSet.Get( ATTR_FONT_OVERLINE ) ); + rField.SetCrossedOut ( rItemSet.Get( ATTR_FONT_CROSSEDOUT ) ); + rField.SetContour ( rItemSet.Get( ATTR_FONT_CONTOUR ) ); + rField.SetShadowed ( rItemSet.Get( ATTR_FONT_SHADOWED ) ); + rField.SetColor ( rItemSet.Get( ATTR_FONT_COLOR ) ); + rField.SetTLBR ( rItemSet.Get( ATTR_BORDER_TLBR ) ); + rField.SetBLTR ( rItemSet.Get( ATTR_BORDER_BLTR ) ); + rField.SetHorJustify ( rItemSet.Get( ATTR_HOR_JUSTIFY ) ); + rField.SetVerJustify ( rItemSet.Get( ATTR_VER_JUSTIFY ) ); + rField.SetStacked ( rItemSet.Get( ATTR_STACKED ) ); + rField.SetLinebreak ( rItemSet.Get( ATTR_LINEBREAK ) ); + rField.SetMargin ( rItemSet.Get( ATTR_MARGIN ) ); + rField.SetBackground ( rItemSet.Get( ATTR_BACKGROUND ) ); + rField.SetRotateAngle ( rItemSet.Get( ATTR_ROTATE_VALUE ) ); + rField.SetRotateMode ( rItemSet.Get( ATTR_ROTATE_MODE ) ); +} + +const TranslateId RID_SVXSTR_TBLAFMT[] = +{ + RID_SVXSTR_TBLAFMT_3D, + RID_SVXSTR_TBLAFMT_BLACK1, + RID_SVXSTR_TBLAFMT_BLACK2, + RID_SVXSTR_TBLAFMT_BLUE, + RID_SVXSTR_TBLAFMT_BROWN, + RID_SVXSTR_TBLAFMT_CURRENCY, + RID_SVXSTR_TBLAFMT_CURRENCY_3D, + RID_SVXSTR_TBLAFMT_CURRENCY_GRAY, + RID_SVXSTR_TBLAFMT_CURRENCY_LAVENDER, + RID_SVXSTR_TBLAFMT_CURRENCY_TURQUOISE, + RID_SVXSTR_TBLAFMT_GRAY, + RID_SVXSTR_TBLAFMT_GREEN, + RID_SVXSTR_TBLAFMT_LAVENDER, + RID_SVXSTR_TBLAFMT_RED, + RID_SVXSTR_TBLAFMT_TURQUOISE, + RID_SVXSTR_TBLAFMT_YELLOW, + RID_SVXSTR_TBLAFMT_LO6_ACADEMIC, + RID_SVXSTR_TBLAFMT_LO6_BOX_LIST_BLUE, + RID_SVXSTR_TBLAFMT_LO6_BOX_LIST_GREEN, + RID_SVXSTR_TBLAFMT_LO6_BOX_LIST_RED, + RID_SVXSTR_TBLAFMT_LO6_BOX_LIST_YELLOW, + RID_SVXSTR_TBLAFMT_LO6_ELEGANT, + RID_SVXSTR_TBLAFMT_LO6_FINANCIAL, + RID_SVXSTR_TBLAFMT_LO6_SIMPLE_GRID_COLUMNS, + RID_SVXSTR_TBLAFMT_LO6_SIMPLE_GRID_ROWS, + RID_SVXSTR_TBLAFMT_LO6_SIMPLE_LIST_SHADED +}; + +bool ScAutoFormatData::Load( SvStream& rStream, const ScAfVersions& rVersions ) +{ + sal_uInt16 nVer = 0; + rStream.ReadUInt16( nVer ); + bool bRet = ERRCODE_NONE == rStream.GetError(); + if( bRet && (nVer == AUTOFORMAT_DATA_ID_X || + (AUTOFORMAT_DATA_ID_504 <= nVer && nVer <= AUTOFORMAT_DATA_ID)) ) + { + // --- from 680/dr25 on: store strings as UTF-8 + if (nVer >= AUTOFORMAT_ID_680DR25) + { + aName = read_uInt16_lenPrefixed_uInt8s_ToOUString(rStream, + RTL_TEXTENCODING_UTF8); + } + else + aName = rStream.ReadUniOrByteString( rStream.GetStreamCharSet() ); + + if( AUTOFORMAT_DATA_ID_552 <= nVer ) + { + rStream.ReadUInt16( nStrResId ); + if (nStrResId < SAL_N_ELEMENTS(RID_SVXSTR_TBLAFMT)) + aName = SvxResId(RID_SVXSTR_TBLAFMT[nStrResId]); + else + nStrResId = USHRT_MAX; + } + + bool b; + rStream.ReadCharAsBool( b ); bIncludeFont = b; + rStream.ReadCharAsBool( b ); bIncludeJustify = b; + rStream.ReadCharAsBool( b ); bIncludeFrame = b; + rStream.ReadCharAsBool( b ); bIncludeBackground = b; + rStream.ReadCharAsBool( b ); bIncludeValueFormat = b; + rStream.ReadCharAsBool( b ); bIncludeWidthHeight = b; + + if (nVer >= AUTOFORMAT_DATA_ID_31005) + rStream >> m_swFields; + + bRet = ERRCODE_NONE == rStream.GetError(); + for( sal_uInt16 i = 0; bRet && i < 16; ++i ) + bRet = GetField( i ).Load( rStream, rVersions, nVer ); + } + else + bRet = false; + return bRet; +} + +bool ScAutoFormatData::Save(SvStream& rStream, sal_uInt16 fileVersion) +{ + rStream.WriteUInt16( AUTOFORMAT_DATA_ID ); + // --- from 680/dr25 on: store strings as UTF-8 + write_uInt16_lenPrefixed_uInt8s_FromOUString(rStream, aName, RTL_TEXTENCODING_UTF8); + + rStream.WriteUInt16( nStrResId ); + rStream.WriteBool( bIncludeFont ); + rStream.WriteBool( bIncludeJustify ); + rStream.WriteBool( bIncludeFrame ); + rStream.WriteBool( bIncludeBackground ); + rStream.WriteBool( bIncludeValueFormat ); + rStream.WriteBool( bIncludeWidthHeight ); + + if (fileVersion >= SOFFICE_FILEFORMAT_50) + WriteAutoFormatSwBlob( rStream, m_swFields ); + + bool bRet = ERRCODE_NONE == rStream.GetError(); + for (sal_uInt16 i = 0; bRet && (i < 16); i++) + bRet = GetField( i ).Save( rStream, fileVersion ); + + return bRet; +} + +ScAutoFormat::ScAutoFormat() : + mbSaveLater(false) +{ + // create default autoformat + std::unique_ptr<ScAutoFormatData> pData(new ScAutoFormatData); + OUString aName(ScResId(STR_STYLENAME_STANDARD)); + pData->SetName(aName); + + // default font, default height + vcl::Font aStdFont = OutputDevice::GetDefaultFont( + DefaultFontType::LATIN_SPREADSHEET, LANGUAGE_ENGLISH_US, GetDefaultFontFlags::OnlyOne ); + SvxFontItem aFontItem( + aStdFont.GetFamilyType(), aStdFont.GetFamilyName(), aStdFont.GetStyleName(), + aStdFont.GetPitch(), aStdFont.GetCharSet(), ATTR_FONT ); + + aStdFont = OutputDevice::GetDefaultFont( + DefaultFontType::CJK_SPREADSHEET, LANGUAGE_ENGLISH_US, GetDefaultFontFlags::OnlyOne ); + SvxFontItem aCJKFontItem( + aStdFont.GetFamilyType(), aStdFont.GetFamilyName(), aStdFont.GetStyleName(), + aStdFont.GetPitch(), aStdFont.GetCharSet(), ATTR_CJK_FONT ); + + aStdFont = OutputDevice::GetDefaultFont( + DefaultFontType::CTL_SPREADSHEET, LANGUAGE_ENGLISH_US, GetDefaultFontFlags::OnlyOne ); + SvxFontItem aCTLFontItem( + aStdFont.GetFamilyType(), aStdFont.GetFamilyName(), aStdFont.GetStyleName(), + aStdFont.GetPitch(), aStdFont.GetCharSet(), ATTR_CTL_FONT ); + + SvxFontHeightItem aHeight( 200, 100, ATTR_FONT_HEIGHT ); // 10 pt; + + // black thin border + Color aBlack( COL_BLACK ); + ::editeng::SvxBorderLine aLine( &aBlack, SvxBorderLineWidth::VeryThin ); + SvxBoxItem aBox( ATTR_BORDER ); + aBox.SetLine(&aLine, SvxBoxItemLine::LEFT); + aBox.SetLine(&aLine, SvxBoxItemLine::TOP); + aBox.SetLine(&aLine, SvxBoxItemLine::RIGHT); + aBox.SetLine(&aLine, SvxBoxItemLine::BOTTOM); + + Color aWhite(COL_WHITE); + SvxColorItem aWhiteText( aWhite, ATTR_FONT_COLOR ); + SvxColorItem aBlackText( aBlack, ATTR_FONT_COLOR ); + SvxBrushItem aBlueBack( COL_BLUE, ATTR_BACKGROUND ); + SvxBrushItem aWhiteBack( aWhite, ATTR_BACKGROUND ); + SvxBrushItem aGray70Back( Color(0x4d, 0x4d, 0x4d), ATTR_BACKGROUND ); + SvxBrushItem aGray20Back( Color(0xcc, 0xcc, 0xcc), ATTR_BACKGROUND ); + + for (sal_uInt16 i=0; i<16; i++) + { + pData->PutItem( i, aBox ); + pData->PutItem( i, aFontItem ); + pData->PutItem( i, aCJKFontItem ); + pData->PutItem( i, aCTLFontItem ); + aHeight.SetWhich( ATTR_FONT_HEIGHT ); + pData->PutItem( i, aHeight ); + aHeight.SetWhich( ATTR_CJK_FONT_HEIGHT ); + pData->PutItem( i, aHeight ); + aHeight.SetWhich( ATTR_CTL_FONT_HEIGHT ); + pData->PutItem( i, aHeight ); + if (i<4) // top: white on blue + { + pData->PutItem( i, aWhiteText ); + pData->PutItem( i, aBlueBack ); + } + else if ( i%4 == 0 ) // left: white on gray70 + { + pData->PutItem( i, aWhiteText ); + pData->PutItem( i, aGray70Back ); + } + else if ( i%4 == 3 || i >= 12 ) // right and bottom: black on gray20 + { + pData->PutItem( i, aBlackText ); + pData->PutItem( i, aGray20Back ); + } + else // center: black on white + { + pData->PutItem( i, aBlackText ); + pData->PutItem( i, aWhiteBack ); + } + } + + insert(std::move(pData)); +} + +bool DefaultFirstEntry::operator() (const OUString& left, const OUString& right) const +{ + OUString aStrStandard(ScResId(STR_STYLENAME_STANDARD)); + if (ScGlobal::GetTransliteration().isEqual( left, right ) ) + return false; + if ( ScGlobal::GetTransliteration().isEqual( left, aStrStandard ) ) + return true; + if ( ScGlobal::GetTransliteration().isEqual( right, aStrStandard ) ) + return false; + return ScGlobal::GetCollator().compareString( left, right) < 0; +} + +void ScAutoFormat::SetSaveLater( bool bSet ) +{ + mbSaveLater = bSet; +} + +const ScAutoFormatData* ScAutoFormat::findByIndex(size_t nIndex) const +{ + if (nIndex >= m_Data.size()) + return nullptr; + + MapType::const_iterator it = m_Data.begin(); + std::advance(it, nIndex); + return it->second.get(); +} + +ScAutoFormatData* ScAutoFormat::findByIndex(size_t nIndex) +{ + if (nIndex >= m_Data.size()) + return nullptr; + + MapType::iterator it = m_Data.begin(); + std::advance(it, nIndex); + return it->second.get(); +} + +ScAutoFormat::iterator ScAutoFormat::find(const OUString& rName) +{ + return m_Data.find(rName); +} + +ScAutoFormat::iterator ScAutoFormat::insert(std::unique_ptr<ScAutoFormatData> pNew) +{ + OUString aName = pNew->GetName(); + return m_Data.insert(std::make_pair(aName, std::move(pNew))).first; +} + +void ScAutoFormat::erase(const iterator& it) +{ + m_Data.erase(it); +} + +size_t ScAutoFormat::size() const +{ + return m_Data.size(); +} + +ScAutoFormat::const_iterator ScAutoFormat::begin() const +{ + return m_Data.begin(); +} + +ScAutoFormat::const_iterator ScAutoFormat::end() const +{ + return m_Data.end(); +} + +ScAutoFormat::iterator ScAutoFormat::begin() +{ + return m_Data.begin(); +} + +ScAutoFormat::iterator ScAutoFormat::end() +{ + return m_Data.end(); +} + +void ScAutoFormat::Load() +{ + INetURLObject aURL; + SvtPathOptions aPathOpt; + aURL.SetSmartURL( aPathOpt.GetUserConfigPath() ); + aURL.setFinalSlash(); + aURL.Append( sAutoTblFmtName ); + + SfxMedium aMedium( aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE), StreamMode::READ ); + SvStream* pStream = aMedium.GetInStream(); + bool bRet = (pStream && pStream->GetError() == ERRCODE_NONE); + if (bRet) + { + SvStream& rStream = *pStream; + // Attention: A common header has to be read + sal_uInt16 nVal = 0; + rStream.ReadUInt16( nVal ); + bRet = ERRCODE_NONE == rStream.GetError(); + + if (bRet) + { + if( nVal == AUTOFORMAT_ID_358 || + (AUTOFORMAT_ID_504 <= nVal && nVal <= AUTOFORMAT_ID) ) + { + sal_uInt8 nChrSet, nCnt; + sal_uInt64 nPos = rStream.Tell(); + rStream.ReadUChar( nCnt ).ReadUChar( nChrSet ); + if( rStream.Tell() != sal_uLong(nPos + nCnt) ) + { + OSL_FAIL( "header contains more/newer data" ); + rStream.Seek( nPos + nCnt ); + } + rStream.SetStreamCharSet( GetSOLoadTextEncoding( nChrSet ) ); + rStream.SetVersion( SOFFICE_FILEFORMAT_40 ); + } + + if( nVal == AUTOFORMAT_ID_358 || nVal == AUTOFORMAT_ID_X || + (AUTOFORMAT_ID_504 <= nVal && nVal <= AUTOFORMAT_ID) ) + { + m_aVersions.Load( rStream, nVal ); // item versions + + sal_uInt16 nCnt = 0; + rStream.ReadUInt16( nCnt ); + bRet = (rStream.GetError() == ERRCODE_NONE); + + // there has to at least be a sal_uInt16 header + const size_t nMaxRecords = rStream.remainingSize() / sizeof(sal_uInt16); + if (nCnt > nMaxRecords) + { + SAL_WARN("sc", "Parsing error: " << nMaxRecords << + " max possible entries, but " << nCnt << " claimed, truncating"); + nCnt = nMaxRecords; + } + + for (sal_uInt16 i=0; bRet && (i < nCnt); i++) + { + std::unique_ptr<ScAutoFormatData> pData(new ScAutoFormatData()); + bRet = pData->Load(rStream, m_aVersions); + insert(std::move(pData)); + } + } + } + } + mbSaveLater = false; +} + +bool ScAutoFormat::Save() +{ + INetURLObject aURL; + SvtPathOptions aPathOpt; + aURL.SetSmartURL( aPathOpt.GetUserConfigPath() ); + aURL.setFinalSlash(); + aURL.Append(sAutoTblFmtName); + + SfxMedium aMedium( aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE), StreamMode::WRITE ); + SvStream* pStream = aMedium.GetOutStream(); + bool bRet = (pStream && pStream->GetError() == ERRCODE_NONE); + if (bRet) + { + const sal_uInt16 fileVersion = SOFFICE_FILEFORMAT_50; + SvStream& rStream = *pStream; + rStream.SetVersion( fileVersion ); + + // Attention: A common header has to be saved + rStream.WriteUInt16( AUTOFORMAT_ID ) + .WriteUChar( 2 ) // Number of chars of the header including this + .WriteUChar( ::GetSOStoreTextEncoding( + osl_getThreadTextEncoding() ) ); + m_aVersions.Write(rStream, fileVersion); + + bRet &= (rStream.GetError() == ERRCODE_NONE); + + rStream.WriteUInt16( m_Data.size() - 1 ); + bRet &= (rStream.GetError() == ERRCODE_NONE); + MapType::iterator it = m_Data.begin(), itEnd = m_Data.end(); + if (it != itEnd) + { + for (++it; bRet && it != itEnd; ++it) // Skip the first item. + { + bRet &= it->second->Save(rStream, fileVersion); + } + } + + rStream.FlushBuffer(); + + aMedium.Commit(); + } + mbSaveLater = false; + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/calcconfig.cxx b/sc/source/core/tool/calcconfig.cxx new file mode 100644 index 0000000000..7eb36d73a9 --- /dev/null +++ b/sc/source/core/tool/calcconfig.cxx @@ -0,0 +1,241 @@ +/* -*- 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/. + */ + +#include <ostream> + +#include <formula/FormulaCompiler.hxx> +#include <formula/grammar.hxx> +#include <formula/opcode.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <unotools/configmgr.hxx> + +#include <calcconfig.hxx> + +#include <comphelper/configurationlistener.hxx> + +using comphelper::ConfigurationListener; + +static rtl::Reference<ConfigurationListener> const & getMiscListener() +{ + static rtl::Reference<ConfigurationListener> xListener(new ConfigurationListener("/org.openoffice.Office.Common/Misc")); + return xListener; +} + +static rtl::Reference<ConfigurationListener> const & getFormulaCalculationListener() +{ + static rtl::Reference<ConfigurationListener> xListener(new ConfigurationListener("/org.openoffice.Office.Calc/Formula/Calculation")); + return xListener; +} + +static ForceCalculationType forceCalculationTypeInit() +{ + const char* env = getenv( "SC_FORCE_CALCULATION" ); + if( env != nullptr ) + { + if( strcmp( env, "opencl" ) == 0 ) + { + SAL_INFO("sc.core.formulagroup", "Forcing calculations to use OpenCL"); + return ForceCalculationOpenCL; + } + if( strcmp( env, "threads" ) == 0 ) + { + SAL_INFO("sc.core.formulagroup", "Forcing calculations to use threads"); + return ForceCalculationThreads; + } + if( strcmp( env, "core" ) == 0 ) + { + SAL_INFO("sc.core.formulagroup", "Forcing calculations to use core"); + return ForceCalculationCore; + } + SAL_WARN("sc.core.formulagroup", "Unrecognized value of SC_FORCE_CALCULATION"); + abort(); + } + return ForceCalculationNone; +} + +ForceCalculationType ScCalcConfig::getForceCalculationType() +{ + static const ForceCalculationType type = forceCalculationTypeInit(); + return type; +} + +bool ScCalcConfig::isOpenCLEnabled() +{ + if (utl::ConfigManager::IsFuzzing()) + return false; + static ForceCalculationType force = getForceCalculationType(); + if( force != ForceCalculationNone ) + return force == ForceCalculationOpenCL; + static comphelper::ConfigurationListenerProperty<bool> gOpenCLEnabled(getMiscListener(), "UseOpenCL"); + return gOpenCLEnabled.get(); +} + +bool ScCalcConfig::isThreadingEnabled() +{ + if (utl::ConfigManager::IsFuzzing()) + return false; + static ForceCalculationType force = getForceCalculationType(); + if( force != ForceCalculationNone ) + return force == ForceCalculationThreads; + static comphelper::ConfigurationListenerProperty<bool> gThreadingEnabled(getFormulaCalculationListener(), "UseThreadedCalculationForFormulaGroups"); + return gThreadingEnabled.get(); +} + +ScCalcConfig::ScCalcConfig() : + meStringRefAddressSyntax(formula::FormulaGrammar::CONV_UNSPECIFIED), + meStringConversion(StringConversion::LOCALE), // old LibreOffice behavior + mbEmptyStringAsZero(false), + mbHasStringRefSyntax(false) +{ + setOpenCLConfigToDefault(); +} + +void ScCalcConfig::setOpenCLConfigToDefault() +{ + // Keep in order of opcode value, is that clearest? (Random order, + // at least, would make no sense at all.) + static const OpCodeSet pDefaultOpenCLSubsetOpCodes(new o3tl::sorted_vector<OpCode>({ + ocAdd, + ocSub, + ocNegSub, + ocMul, + ocDiv, + ocPow, + ocRandom, + ocSin, + ocCos, + ocTan, + ocArcTan, + ocExp, + ocLn, + ocSqrt, + ocStdNormDist, + ocSNormInv, + ocRound, + ocPower, + ocSumProduct, + ocMin, + ocMax, + ocSum, + ocProduct, + ocAverage, + ocCount, + ocVar, + ocNormDist, + ocVLookup, + ocCorrel, + ocCovar, + ocPearson, + ocSlope, + ocSumIfs})); + + // Note that these defaults better be kept in sync with those in + // officecfg/registry/schema/org/openoffice/Office/Calc.xcs. + // Crazy. + mbOpenCLSubsetOnly = true; + mbOpenCLAutoSelect = true; + mnOpenCLMinimumFormulaGroupSize = 100; + mpOpenCLSubsetOpCodes = pDefaultOpenCLSubsetOpCodes; +} + +void ScCalcConfig::reset() +{ + *this = ScCalcConfig(); +} + +void ScCalcConfig::MergeDocumentSpecific( const ScCalcConfig& r ) +{ + // String conversion options are per document. + meStringConversion = r.meStringConversion; + mbEmptyStringAsZero = r.mbEmptyStringAsZero; + // INDIRECT ref syntax is per document. + meStringRefAddressSyntax = r.meStringRefAddressSyntax; + mbHasStringRefSyntax = r.mbHasStringRefSyntax; +} + +void ScCalcConfig::SetStringRefSyntax( formula::FormulaGrammar::AddressConvention eConv ) +{ + meStringRefAddressSyntax = eConv; + mbHasStringRefSyntax = true; +} + +bool ScCalcConfig::operator== (const ScCalcConfig& r) const +{ + return meStringRefAddressSyntax == r.meStringRefAddressSyntax && + meStringConversion == r.meStringConversion && + mbEmptyStringAsZero == r.mbEmptyStringAsZero && + mbHasStringRefSyntax == r.mbHasStringRefSyntax && + mbOpenCLSubsetOnly == r.mbOpenCLSubsetOnly && + mbOpenCLAutoSelect == r.mbOpenCLAutoSelect && + maOpenCLDevice == r.maOpenCLDevice && + mnOpenCLMinimumFormulaGroupSize == r.mnOpenCLMinimumFormulaGroupSize && + *mpOpenCLSubsetOpCodes == *r.mpOpenCLSubsetOpCodes; +} + +bool ScCalcConfig::operator!= (const ScCalcConfig& r) const +{ + return !operator==(r); +} + +OUString ScOpCodeSetToSymbolicString(const ScCalcConfig::OpCodeSet& rOpCodes) +{ + OUStringBuffer result(256); + formula::FormulaCompiler aCompiler; + formula::FormulaCompiler::OpCodeMapPtr pOpCodeMap(aCompiler.GetOpCodeMap(css::sheet::FormulaLanguage::ENGLISH)); + + for (auto i = rOpCodes->begin(); i != rOpCodes->end(); ++i) + { + if (i != rOpCodes->begin()) + result.append(';'); + result.append(pOpCodeMap->getSymbol(*i)); + } + + return result.makeStringAndClear(); +} + +ScCalcConfig::OpCodeSet ScStringToOpCodeSet(std::u16string_view rOpCodes) +{ + ScCalcConfig::OpCodeSet result = std::make_shared<o3tl::sorted_vector< OpCode >>(); + formula::FormulaCompiler aCompiler; + formula::FormulaCompiler::OpCodeMapPtr pOpCodeMap(aCompiler.GetOpCodeMap(css::sheet::FormulaLanguage::ENGLISH)); + + const formula::OpCodeHashMap& rHashMap(pOpCodeMap->getHashMap()); + + sal_Int32 fromIndex(0); + sal_Int32 semicolon; + OUString s(OUString::Concat(rOpCodes) + ";"); + + while ((semicolon = s.indexOf(';', fromIndex)) >= 0) + { + if (semicolon > fromIndex) + { + OUString element(s.copy(fromIndex, semicolon - fromIndex)); + sal_Int32 n = element.toInt32(); + if (n > 0 || (n == 0 && element == "0")) + result->insert(static_cast<OpCode>(n)); + else + { + auto opcode(rHashMap.find(element)); + if (opcode != rHashMap.end()) + result->insert(opcode->second); + else + SAL_WARN("sc.opencl", "Unrecognized OpCode " << element << " in OpCode set string"); + } + } + fromIndex = semicolon+1; + } + // HACK: Both unary and binary minus have the same string but different opcodes. + if( result->find( ocSub ) != result->end()) + result->insert( ocNegSub ); + + return result; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/callform.cxx b/sc/source/core/tool/callform.cxx new file mode 100644 index 0000000000..2c441164cf --- /dev/null +++ b/sc/source/core/tool/callform.cxx @@ -0,0 +1,412 @@ +/* -*- 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 <sal/config.h> + +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <osl/module.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <memory> + +#include <callform.hxx> +#include <global.hxx> +#include <adiasync.hxx> + +extern "C" { + +typedef void (CALLTYPE* ExFuncPtr1)(void*); +typedef void (CALLTYPE* ExFuncPtr2)(void*, void*); +typedef void (CALLTYPE* ExFuncPtr3)(void*, void*, void*); +typedef void (CALLTYPE* ExFuncPtr4)(void*, void*, void*, void*); +typedef void (CALLTYPE* ExFuncPtr5)(void*, void*, void*, void*, void*); +typedef void (CALLTYPE* ExFuncPtr6)(void*, void*, void*, void*, void*, void*); +typedef void (CALLTYPE* ExFuncPtr7)(void*, void*, void*, void*, void*, void*, void*); +typedef void (CALLTYPE* ExFuncPtr8)(void*, void*, void*, void*, void*, void*, void*, void*); +typedef void (CALLTYPE* ExFuncPtr9)(void*, void*, void*, void*, void*, void*, void*, void*, void*); +typedef void (CALLTYPE* ExFuncPtr10)(void*, void*, void*, void*, void*, void*, void*, void*, void*, void*); +typedef void (CALLTYPE* ExFuncPtr11)(void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*); +typedef void (CALLTYPE* ExFuncPtr12)(void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*); +typedef void (CALLTYPE* ExFuncPtr13)(void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*); +typedef void (CALLTYPE* ExFuncPtr14)(void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*); +typedef void (CALLTYPE* ExFuncPtr15)(void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*); +typedef void (CALLTYPE* ExFuncPtr16)(void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*, void*); + +typedef void (CALLTYPE* GetFuncCountPtr)(sal_uInt16& nCount); +typedef void (CALLTYPE* GetFuncDataPtr) + (sal_uInt16& nNo, char* pFuncName, sal_uInt16& nParamCount, ParamType* peType, char* pInternalName); + +typedef void (CALLTYPE* SetLanguagePtr)( sal_uInt16& nLanguage ); +typedef void (CALLTYPE* GetParamDesc) + (sal_uInt16& nNo, sal_uInt16& nParam, char* pName, char* pDesc ); + +typedef void (CALLTYPE* IsAsync) ( sal_uInt16& nNo, + ParamType* peType ); +typedef void (CALLTYPE* Advice) ( sal_uInt16& nNo, + AdvData& pfCallback ); +typedef void (CALLTYPE* Unadvice)( double& nHandle ); + +} + +#ifndef DISABLE_DYNLOADING +constexpr OUStringLiteral GETFUNCTIONCOUNT = u"GetFunctionCount"; +constexpr OUStringLiteral GETFUNCTIONDATA = u"GetFunctionData"; +constexpr OUStringLiteral SETLANGUAGE = u"SetLanguage"; +constexpr OUStringLiteral GETPARAMDESC = u"GetParameterDescription"; +constexpr OUStringLiteral ISASYNC = u"IsAsync"; +constexpr OUStringLiteral ADVICE = u"Advice"; +constexpr OUStringLiteral UNADVICE = u"Unadvice"; +#endif + +class ModuleData +{ +friend class ModuleCollection; + OUString aName; + std::unique_ptr<osl::Module> pInstance; +public: + ModuleData(const ModuleData&) = delete; + const ModuleData& operator=(const ModuleData&) = delete; + + ModuleData(OUString aStr, std::unique_ptr<osl::Module> pInst) : aName(std::move(aStr)), pInstance(std::move(pInst)) {} + + const OUString& GetName() const { return aName; } + osl::Module* GetInstance() const { return pInstance.get(); } +}; + +LegacyFuncData::LegacyFuncData(const ModuleData*pModule, + OUString aIName, + OUString aFName, + sal_uInt16 nNo, + sal_uInt16 nCount, + const ParamType* peType, + ParamType eType) : + pModuleData (pModule), + aInternalName (std::move(aIName)), + aFuncName (std::move(aFName)), + nNumber (nNo), + nParamCount (nCount), + eAsyncType (eType) +{ + for (sal_uInt16 i = 0; i < MAXFUNCPARAM; i++) + eParamType[i] = peType[i]; +} + +LegacyFuncData::LegacyFuncData(const LegacyFuncData& rData) : + pModuleData (rData.pModuleData), + aInternalName (rData.aInternalName), + aFuncName (rData.aFuncName), + nNumber (rData.nNumber), + nParamCount (rData.nParamCount), + eAsyncType (rData.eAsyncType) +{ + for (sal_uInt16 i = 0; i < MAXFUNCPARAM; i++) + eParamType[i] = rData.eParamType[i]; +} + +namespace { + +class ModuleCollection +{ + typedef std::map<OUString, std::unique_ptr<ModuleData>> MapType; + MapType m_Data; +public: + ModuleCollection() {} + + const ModuleData* findByName(const OUString& rName) const; + void insert(ModuleData* pNew); + void clear(); +}; + +const ModuleData* ModuleCollection::findByName(const OUString& rName) const +{ + MapType::const_iterator it = m_Data.find(rName); + return it == m_Data.end() ? nullptr : it->second.get(); +} + +void ModuleCollection::insert(ModuleData* pNew) +{ + if (!pNew) + return; + + OUString aName = pNew->GetName(); + m_Data.insert(std::make_pair(aName, std::unique_ptr<ModuleData>(pNew))); +} + +void ModuleCollection::clear() +{ + m_Data.clear(); +} + +ModuleCollection aModuleCollection; + +} + +bool InitExternalFunc(const OUString& rModuleName) +{ +#ifdef DISABLE_DYNLOADING + (void) rModuleName; + return false; +#else + // Module already loaded? + const ModuleData* pTemp = aModuleCollection.findByName(rModuleName); + if (pTemp) + return false; + + OUString aNP = rModuleName; + + std::unique_ptr<osl::Module> pLib(new osl::Module( aNP )); + if (!pLib->is()) + return false; + + oslGenericFunction fpGetCount = pLib->getFunctionSymbol(GETFUNCTIONCOUNT); + oslGenericFunction fpGetData = pLib->getFunctionSymbol(GETFUNCTIONDATA); + if ((fpGetCount == nullptr) || (fpGetData == nullptr)) + return false; + + oslGenericFunction fpIsAsync = pLib->getFunctionSymbol(ISASYNC); + oslGenericFunction fpAdvice = pLib->getFunctionSymbol(ADVICE); + oslGenericFunction fpSetLanguage = pLib->getFunctionSymbol(SETLANGUAGE); + if ( fpSetLanguage ) + { + LanguageType eLanguage = Application::GetSettings().GetUILanguageTag().getLanguageType(); + sal_uInt16 nLanguage = static_cast<sal_uInt16>(eLanguage); + (*reinterpret_cast<SetLanguagePtr>(fpSetLanguage))( nLanguage ); + } + + // include module into the collection + ModuleData* pModuleData = new ModuleData(rModuleName, std::move(pLib)); + aModuleCollection.insert(pModuleData); + + // initialize interface + AdvData pfCallBack = &ScAddInAsyncCallBack; + LegacyFuncCollection* pLegacyFuncCol = ScGlobal::GetLegacyFuncCollection(); + sal_uInt16 nCount; + (*reinterpret_cast<GetFuncCountPtr>(fpGetCount))(nCount); + for (sal_uInt16 i=0; i < nCount; i++) + { + char cFuncName[256]; + char cInternalName[256]; + sal_uInt16 nParamCount; + ParamType eParamType[MAXFUNCPARAM]; + ParamType eAsyncType = ParamType::NONE; + // initialize all, in case the AddIn behaves bad + cFuncName[0] = 0; + cInternalName[0] = 0; + nParamCount = 0; + for (ParamType & rParamType : eParamType) + { + rParamType = ParamType::NONE; + } + (*reinterpret_cast<GetFuncDataPtr>(fpGetData))(i, cFuncName, nParamCount, + eParamType, cInternalName); + if( fpIsAsync ) + { + (*reinterpret_cast<IsAsync>(fpIsAsync))(i, &eAsyncType); + if ( fpAdvice && eAsyncType != ParamType::NONE ) + (*reinterpret_cast<Advice>(fpAdvice))( i, pfCallBack ); + } + OUString aInternalName( cInternalName, strlen(cInternalName), osl_getThreadTextEncoding() ); + OUString aFuncName( cFuncName, strlen(cFuncName), osl_getThreadTextEncoding() ); + LegacyFuncData* pLegacyFuncData = new LegacyFuncData( pModuleData, + aInternalName, + aFuncName, + i, + nParamCount, + eParamType, + eAsyncType ); + pLegacyFuncCol->insert(pLegacyFuncData); + } + return true; +#endif +} + +void ExitExternalFunc() +{ + aModuleCollection.clear(); +} + +void LegacyFuncData::Call(void** ppParam) const +{ +#ifdef DISABLE_DYNLOADING + (void) ppParam; +#else + osl::Module* pLib = pModuleData->GetInstance(); + oslGenericFunction fProc = pLib->getFunctionSymbol(aFuncName); + if (fProc == nullptr) + return; + + switch (nParamCount) + { + case 1 : + (*reinterpret_cast<ExFuncPtr1>(fProc))(ppParam[0]); + break; + case 2 : + (*reinterpret_cast<ExFuncPtr2>(fProc))(ppParam[0], ppParam[1]); + break; + case 3 : + (*reinterpret_cast<ExFuncPtr3>(fProc))(ppParam[0], ppParam[1], ppParam[2]); + break; + case 4 : + (*reinterpret_cast<ExFuncPtr4>(fProc))(ppParam[0], ppParam[1], ppParam[2], ppParam[3]); + break; + case 5 : + (*reinterpret_cast<ExFuncPtr5>(fProc))(ppParam[0], ppParam[1], ppParam[2], ppParam[3], ppParam[4]); + break; + case 6 : + (*reinterpret_cast<ExFuncPtr6>(fProc))(ppParam[0], ppParam[1], ppParam[2], ppParam[3], ppParam[4], ppParam[5]); + break; + case 7 : + (*reinterpret_cast<ExFuncPtr7>(fProc))( ppParam[0], ppParam[1], ppParam[2], ppParam[3], ppParam[4], ppParam[5], + ppParam[6]); + break; + case 8 : + (*reinterpret_cast<ExFuncPtr8>(fProc))( ppParam[0], ppParam[1], ppParam[2], ppParam[3], ppParam[4], ppParam[5], + ppParam[6], ppParam[7]); + break; + case 9 : + (*reinterpret_cast<ExFuncPtr9>(fProc))( ppParam[0], ppParam[1], ppParam[2], ppParam[3], ppParam[4], ppParam[5], + ppParam[6], ppParam[7], ppParam[8]); + break; + case 10 : + (*reinterpret_cast<ExFuncPtr10>(fProc))( ppParam[0], ppParam[1], ppParam[2], ppParam[3], ppParam[4], ppParam[5], + ppParam[6], ppParam[7], ppParam[8], ppParam[9]); + break; + case 11 : + (*reinterpret_cast<ExFuncPtr11>(fProc))( ppParam[0], ppParam[1], ppParam[2], ppParam[3], ppParam[4], ppParam[5], + ppParam[6], ppParam[7], ppParam[8], ppParam[9], ppParam[10]); + break; + case 12: + (*reinterpret_cast<ExFuncPtr12>(fProc))( ppParam[0], ppParam[1], ppParam[2], ppParam[3], ppParam[4], ppParam[5], + ppParam[6], ppParam[7], ppParam[8], ppParam[9], ppParam[10], ppParam[11]); + break; + case 13: + (*reinterpret_cast<ExFuncPtr13>(fProc))( ppParam[0], ppParam[1], ppParam[2], ppParam[3], ppParam[4], ppParam[5], + ppParam[6], ppParam[7], ppParam[8], ppParam[9], ppParam[10], ppParam[11], + ppParam[12]); + break; + case 14 : + (*reinterpret_cast<ExFuncPtr14>(fProc))( ppParam[0], ppParam[1], ppParam[2], ppParam[3], ppParam[4], ppParam[5], + ppParam[6], ppParam[7], ppParam[8], ppParam[9], ppParam[10], ppParam[11], + ppParam[12], ppParam[13]); + break; + case 15 : + (*reinterpret_cast<ExFuncPtr15>(fProc))( ppParam[0], ppParam[1], ppParam[2], ppParam[3], ppParam[4], ppParam[5], + ppParam[6], ppParam[7], ppParam[8], ppParam[9], ppParam[10], ppParam[11], + ppParam[12], ppParam[13], ppParam[14]); + break; + case 16 : + (*reinterpret_cast<ExFuncPtr16>(fProc))( ppParam[0], ppParam[1], ppParam[2], ppParam[3], ppParam[4], ppParam[5], + ppParam[6], ppParam[7], ppParam[8], ppParam[9], ppParam[10], ppParam[11], + ppParam[12], ppParam[13], ppParam[14], ppParam[15]); + break; + default : break; + } +#endif +} + +void LegacyFuncData::Unadvice( double nHandle ) +{ +#ifdef DISABLE_DYNLOADING + (void) nHandle; +#else + osl::Module* pLib = pModuleData->GetInstance(); + oslGenericFunction fProc = pLib->getFunctionSymbol(UNADVICE); + if (fProc != nullptr) + { + reinterpret_cast< ::Unadvice>(fProc)(nHandle); + } +#endif +} + +const OUString& LegacyFuncData::GetModuleName() const +{ + return pModuleData->GetName(); +} + +void LegacyFuncData::getParamDesc( OUString& aName, OUString& aDesc, sal_uInt16 nParam ) const +{ +#ifdef DISABLE_DYNLOADING + (void) aName; + (void) aDesc; + (void) nParam; +#else + bool bRet = false; + if ( nParam <= nParamCount ) + { + osl::Module* pLib = pModuleData->GetInstance(); + oslGenericFunction fProc = pLib->getFunctionSymbol(GETPARAMDESC); + if ( fProc != nullptr ) + { + char pcName[256]; + char pcDesc[256]; + *pcName = *pcDesc = 0; + sal_uInt16 nFuncNo = nNumber; // don't let it mess up via reference... + reinterpret_cast< ::GetParamDesc>(fProc)( nFuncNo, nParam, pcName, pcDesc ); + aName = OUString( pcName, 256, osl_getThreadTextEncoding() ); + aDesc = OUString( pcDesc, 256, osl_getThreadTextEncoding() ); + bRet = true; + } + } + if ( !bRet ) + { + aName.clear(); + aDesc.clear(); + } +#endif +} + +LegacyFuncCollection::LegacyFuncCollection() {} +LegacyFuncCollection::LegacyFuncCollection(const LegacyFuncCollection& r) +{ + for (auto const& it : r.m_Data) + { + m_Data.insert(std::make_pair(it.first, std::make_unique<LegacyFuncData>(*it.second))); + } +} + +const LegacyFuncData* LegacyFuncCollection::findByName(const OUString& rName) const +{ + MapType::const_iterator it = m_Data.find(rName); + return it == m_Data.end() ? nullptr : it->second.get(); +} + +LegacyFuncData* LegacyFuncCollection::findByName(const OUString& rName) +{ + MapType::iterator it = m_Data.find(rName); + return it == m_Data.end() ? nullptr : it->second.get(); +} + +void LegacyFuncCollection::insert(LegacyFuncData* pNew) +{ + OUString aName = pNew->GetInternalName(); + m_Data.insert(std::make_pair(aName, std::unique_ptr<LegacyFuncData>(pNew))); +} + +LegacyFuncCollection::const_iterator LegacyFuncCollection::begin() const +{ + return m_Data.begin(); +} + +LegacyFuncCollection::const_iterator LegacyFuncCollection::end() const +{ + return m_Data.end(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/cellform.cxx b/sc/source/core/tool/cellform.cxx new file mode 100644 index 0000000000..9d1a731929 --- /dev/null +++ b/sc/source/core/tool/cellform.cxx @@ -0,0 +1,217 @@ +/* -*- 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 <cellform.hxx> + +#include <svl/numformat.hxx> +#include <svl/sharedstring.hxx> + +#include <formulacell.hxx> +#include <document.hxx> +#include <cellvalue.hxx> +#include <formula/errorcodes.hxx> +#include <editutil.hxx> + +OUString ScCellFormat::GetString( const ScRefCellValue& rCell, sal_uInt32 nFormat, + const Color** ppColor, SvNumberFormatter& rFormatter, const ScDocument& rDoc, + bool bNullVals, bool bFormula, bool bUseStarFormat ) +{ + *ppColor = nullptr; + + switch (rCell.getType()) + { + case CELLTYPE_STRING: + { + OUString str; + rFormatter.GetOutputString(rCell.getSharedString()->getString(), nFormat, str, ppColor, bUseStarFormat); + return str; + } + case CELLTYPE_EDIT: + { + OUString str; + rFormatter.GetOutputString(rCell.getString(&rDoc), nFormat, str, ppColor ); + return str; + } + case CELLTYPE_VALUE: + { + const double nValue = rCell.getDouble(); + if (!bNullVals && nValue == 0.0) + return OUString(); + else + { + OUString str; + rFormatter.GetOutputString( nValue, nFormat, str, ppColor, bUseStarFormat ); + return str; + } + } + case CELLTYPE_FORMULA: + { + ScFormulaCell* pFCell = rCell.getFormula(); + if ( bFormula ) + { + return pFCell->GetFormula(); + } + else + { + // A macro started from the interpreter, which has + // access to Formula Cells, becomes a CellText, even if + // that triggers further interpretation, except if those + // cells are already being interpreted. + // IdleCalc generally doesn't trigger further interpretation, + // as not to get Err522 (circular). + if ( pFCell->GetDocument().IsInInterpreter() && + (!pFCell->GetDocument().GetMacroInterpretLevel() + || pFCell->IsRunning()) ) + { + return "..."; + } + else + { + const FormulaError nErrCode = pFCell->GetErrCode(); + + if (nErrCode != FormulaError::NONE) + return ScGlobal::GetErrorString(nErrCode); + else if ( pFCell->IsEmptyDisplayedAsString() ) + return OUString(); + else if ( pFCell->IsValue() ) + { + double fValue = pFCell->GetValue(); + if ( !bNullVals && fValue == 0.0 ) + return OUString(); + else + { + OUString str; + rFormatter.GetOutputString( fValue, nFormat, str, ppColor, bUseStarFormat ); + return str; + } + } + else + { + OUString str; + rFormatter.GetOutputString( pFCell->GetString().getString(), + nFormat, str, ppColor, bUseStarFormat ); + return str; + } + } + } + } + default: + return OUString(); + } +} + +OUString ScCellFormat::GetString( + ScDocument& rDoc, const ScAddress& rPos, sal_uInt32 nFormat, const Color** ppColor, + SvNumberFormatter& rFormatter, bool bNullVals, bool bFormula ) +{ + *ppColor = nullptr; + + ScRefCellValue aCell(rDoc, rPos); + return GetString(aCell, nFormat, ppColor, rFormatter, rDoc, bNullVals, bFormula); +} + +OUString ScCellFormat::GetInputString( + const ScRefCellValue& rCell, sal_uInt32 nFormat, SvNumberFormatter& rFormatter, const ScDocument& rDoc, + const svl::SharedString** pShared, bool bFiltering, bool bForceSystemLocale ) +{ + if(pShared != nullptr) + *pShared = nullptr; + switch (rCell.getType()) + { + case CELLTYPE_STRING: + case CELLTYPE_EDIT: + return rCell.getString(&rDoc); + case CELLTYPE_VALUE: + { + OUString str; + rFormatter.GetInputLineString(rCell.getDouble(), nFormat, str, bFiltering, bForceSystemLocale); + return str; + } + break; + case CELLTYPE_FORMULA: + { + std::optional<OUString> str; + ScFormulaCell* pFC = rCell.getFormula(); + if (pFC->IsEmptyDisplayedAsString()) + ; // empty + else if (pFC->IsValue()) + { + str.emplace(); + rFormatter.GetInputLineString(pFC->GetValue(), nFormat, *str, bFiltering, bForceSystemLocale); + } + else + { + const svl::SharedString& shared = pFC->GetString(); + // Allow callers to optimize by avoiding converting later back to OUString. + // To avoid refcounting that won't be needed, do not even return the OUString. + if( pShared != nullptr ) + *pShared = &shared; + else + str = shared.getString(); + } + + const FormulaError nErrCode = pFC->GetErrCode(); + if (nErrCode != FormulaError::NONE) + { + str.reset(); + if( pShared != nullptr ) + *pShared = nullptr; + } + + return str ? std::move(*str) : svl::SharedString::EMPTY_STRING; + } + case CELLTYPE_NONE: + if( pShared != nullptr ) + *pShared = &svl::SharedString::getEmptyString(); + return svl::SharedString::EMPTY_STRING; + default: + return svl::SharedString::EMPTY_STRING; + } +} + +OUString ScCellFormat::GetOutputString( ScDocument& rDoc, const ScAddress& rPos, const ScRefCellValue& rCell ) +{ + if (rCell.isEmpty()) + return OUString(); + + if (rCell.getType() == CELLTYPE_EDIT) + { + // GetString converts line breaks into spaces in EditCell, + // but here we need the line breaks + const EditTextObject* pData = rCell.getEditText(); + if (pData) + { + ScFieldEditEngine& rEngine = rDoc.GetEditEngine(); + rEngine.SetTextCurrentDefaults(*pData); + return rEngine.GetText(); + } + // also do not format EditCells as numbers + // (fitting to output) + return OUString(); + } + else + { + // like in GetString for document (column) + const Color* pColor; + sal_uInt32 nNumFmt = rDoc.GetNumberFormat(rPos); + return GetString(rCell, nNumFmt, &pColor, *rDoc.GetFormatTable(), rDoc); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/cellkeytranslator.cxx b/sc/source/core/tool/cellkeytranslator.cxx new file mode 100644 index 0000000000..8e2218f931 --- /dev/null +++ b/sc/source/core/tool/cellkeytranslator.cxx @@ -0,0 +1,225 @@ +/* -*- 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 <memory> +#include <global.hxx> +#include <cellkeytranslator.hxx> +#include <comphelper/processfactory.hxx> +#include <i18nlangtag/lang.h> +#include <i18nutil/transliteration.hxx> +#include <rtl/ustring.hxx> +#include <unotools/syslocale.hxx> +#include <com/sun/star/uno/Sequence.hxx> + +using ::com::sun::star::uno::Sequence; + +using namespace ::com::sun::star; + +namespace { + +enum LocaleMatch +{ + LOCALE_MATCH_NONE = 0, + LOCALE_MATCH_LANG, + LOCALE_MATCH_LANG_SCRIPT, + LOCALE_MATCH_LANG_SCRIPT_COUNTRY, + LOCALE_MATCH_ALL +}; + +} + +static LocaleMatch lclLocaleCompare(const lang::Locale& rLocale1, const LanguageTag& rLanguageTag2) +{ + LocaleMatch eMatchLevel = LOCALE_MATCH_NONE; + LanguageTag aLanguageTag1( rLocale1); + + if ( aLanguageTag1.getLanguage() == rLanguageTag2.getLanguage() ) + eMatchLevel = LOCALE_MATCH_LANG; + else + return eMatchLevel; + + if ( aLanguageTag1.getScript() == rLanguageTag2.getScript() ) + eMatchLevel = LOCALE_MATCH_LANG_SCRIPT; + else + return eMatchLevel; + + if ( aLanguageTag1.getCountry() == rLanguageTag2.getCountry() ) + eMatchLevel = LOCALE_MATCH_LANG_SCRIPT_COUNTRY; + else + return eMatchLevel; + + if (aLanguageTag1 == rLanguageTag2) + return LOCALE_MATCH_ALL; + + return eMatchLevel; +} + +ScCellKeyword::ScCellKeyword(const char* pName, OpCode eOpCode, const lang::Locale& rLocale) : + mpName(pName), + meOpCode(eOpCode), + mrLocale(rLocale) +{ +} + +::std::unique_ptr<ScCellKeywordTranslator> ScCellKeywordTranslator::spInstance; + +static void lclMatchKeyword(OUString& rName, const ScCellKeywordHashMap& aMap, + OpCode eOpCode, const lang::Locale* pLocale) +{ + ScCellKeywordHashMap::const_iterator itrEnd = aMap.end(); + ScCellKeywordHashMap::const_iterator itr = aMap.find(rName); + + if ( itr == itrEnd || itr->second.empty() ) + // No candidate strings exist. Bail out. + return; + + if ( eOpCode == ocNone && !pLocale ) + { + // Since no locale nor opcode matching is needed, simply return + // the first item on the list. + rName = OUString::createFromAscii( itr->second.front().mpName ); + return; + } + + LanguageTag aLanguageTag( pLocale ? *pLocale : lang::Locale("","","")); + const char* aBestMatchName = itr->second.front().mpName; + LocaleMatch eLocaleMatchLevel = LOCALE_MATCH_NONE; + bool bOpCodeMatched = false; + + for (auto const& elem : itr->second) + { + if ( eOpCode != ocNone && pLocale ) + { + if (elem.meOpCode == eOpCode) + { + LocaleMatch eLevel = lclLocaleCompare(elem.mrLocale, aLanguageTag); + if ( eLevel == LOCALE_MATCH_ALL ) + { + // Name with matching opcode and locale found. + rName = OUString::createFromAscii( elem.mpName ); + return; + } + else if ( eLevel > eLocaleMatchLevel ) + { + // Name with a better matching locale. + eLocaleMatchLevel = eLevel; + aBestMatchName = elem.mpName; + } + else if ( !bOpCodeMatched ) + // At least the opcode matches. + aBestMatchName = elem.mpName; + + bOpCodeMatched = true; + } + } + else if ( eOpCode != ocNone && !pLocale ) + { + if ( elem.meOpCode == eOpCode ) + { + // Name with a matching opcode preferred. + rName = OUString::createFromAscii( elem.mpName ); + return; + } + } + else if ( pLocale ) + { + LocaleMatch eLevel = lclLocaleCompare(elem.mrLocale, aLanguageTag); + if ( eLevel == LOCALE_MATCH_ALL ) + { + // Name with matching locale preferred. + rName = OUString::createFromAscii( elem.mpName ); + return; + } + else if ( eLevel > eLocaleMatchLevel ) + { + // Name with a better matching locale. + eLocaleMatchLevel = eLevel; + aBestMatchName = elem.mpName; + } + } + } + + // No preferred strings found. Return the best matching name. + rName = OUString::createFromAscii(aBestMatchName); +} + +void ScCellKeywordTranslator::transKeyword(OUString& rName, const lang::Locale* pLocale, OpCode eOpCode) +{ + if (!spInstance) + spInstance.reset( new ScCellKeywordTranslator ); + + LanguageType nLang = pLocale ? + LanguageTag(*pLocale).makeFallback().getLanguageType() : ScGlobal::oSysLocale->GetLanguageTag().getLanguageType(); + Sequence<sal_Int32> aOffsets; + rName = spInstance->maTransWrapper.transliterate(rName, nLang, 0, rName.getLength(), &aOffsets); + lclMatchKeyword(rName, spInstance->maStringNameMap, eOpCode, pLocale); +} + +struct TransItem +{ + const sal_Unicode* from; + const char* to; + OpCode func; +}; + +ScCellKeywordTranslator::ScCellKeywordTranslator() : + maTransWrapper( ::comphelper::getProcessComponentContext(), + TransliterationFlags::LOWERCASE_UPPERCASE ) +{ + // The file below has been autogenerated by sc/workben/celltrans/parse.py. + // To add new locale keywords, edit sc/workben/celltrans/keywords_utf16.txt + // and re-run the parse.py script. + // + // All keywords must be uppercase, and the mapping must be from the + // localized keyword to the English keyword. + // + // Make sure that the original keyword file (keywords_utf16.txt) is + // encoded in UCS-2/UTF-16! + + #include "cellkeywords.inl" +} + +ScCellKeywordTranslator::~ScCellKeywordTranslator() +{ +} + +void ScCellKeywordTranslator::addToMap(const OUString& rKey, const char* pName, const lang::Locale& rLocale, OpCode eOpCode) +{ + ScCellKeyword aKeyItem( pName, eOpCode, rLocale ); + + ScCellKeywordHashMap::iterator itrEnd = maStringNameMap.end(); + ScCellKeywordHashMap::iterator itr = maStringNameMap.find(rKey); + + if ( itr == itrEnd ) + { + // New keyword. + std::vector<ScCellKeyword> aVector { aKeyItem }; + maStringNameMap.emplace(rKey, aVector); + } + else + itr->second.push_back(aKeyItem); +} + +void ScCellKeywordTranslator::addToMap(const TransItem* pItems, const lang::Locale& rLocale) +{ + for (sal_uInt16 i = 0; pItems[i].from != nullptr; ++i) + addToMap(OUString(pItems[i].from), pItems[i].to, rLocale, pItems[i].func); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/cellkeywords.inl b/sc/source/core/tool/cellkeywords.inl new file mode 100644 index 0000000000..b56e3eeadf --- /dev/null +++ b/sc/source/core/tool/cellkeywords.inl @@ -0,0 +1,199 @@ +/* + * 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 . + */ + +// This file has been automatically generated. Do not hand-edit this! + + +// French language locale (automatically generated) + +static const lang::Locale aFr("fr", "", ""); + +// pre instantiations of localized function names +static const sal_Unicode cell_address_fr[] = { + 0x0041, 0x0044, 0x0052, 0x0045, 0x0053, 0x0053, 0x0045, 0x0000}; +static const sal_Unicode cell_col_fr[] = { + 0x0043, 0x004F, 0x004C, 0x004F, 0x004E, 0x004E, 0x0045, 0x0000}; +static const sal_Unicode cell_contents_fr[] = { + 0x0043, 0x004F, 0x004E, 0x0054, 0x0045, 0x004E, 0x0055, 0x0000}; +static const sal_Unicode cell_color_fr[] = { + 0x0043, 0x004F, 0x0055, 0x004C, 0x0045, 0x0055, 0x0052, 0x0000}; +static const sal_Unicode cell_width_fr[] = { + 0x004C, 0x0041, 0x0052, 0x0047, 0x0045, 0x0055, 0x0052, 0x0000}; +static const sal_Unicode cell_row_fr[] = { + 0x004C, 0x0049, 0x0047, 0x004E, 0x0045, 0x0000}; +static const sal_Unicode cell_filename_fr[] = { + 0x004E, 0x004F, 0x004D, 0x0046, 0x0049, 0x0043, 0x0048, 0x0049, 0x0045, 0x0052, 0x0000}; +static const sal_Unicode cell_prefix_fr[] = { + 0x0050, 0x0052, 0x0045, 0x0046, 0x0049, 0x0058, 0x0045, 0x0000}; +static const sal_Unicode cell_protect_fr[] = { + 0x0050, 0x0052, 0x004F, 0x0054, 0x0045, 0x0047, 0x0045, 0x0000}; +static const sal_Unicode info_numfile_fr[] = { + 0x004E, 0x0042, 0x0046, 0x0049, 0x0043, 0x0048, 0x0000}; +static const sal_Unicode info_recalc_fr[] = { + 0x0052, 0x0045, 0x0043, 0x0041, 0x004C, 0x0043, 0x0055, 0x004C, 0x0000}; +static const sal_Unicode info_system_fr[] = { + 0x0053, 0x0059, 0x0053, 0x0054, 0x0045, 0x0058, 0x0050, 0x004C, 0x0000}; +static const sal_Unicode info_release_fr[] = { + 0x0056, 0x0045, 0x0052, 0x0053, 0x0049, 0x004F, 0x004E, 0x0000}; +static const sal_Unicode info_osversion_fr[] = { + 0x0056, 0x0045, 0x0052, 0x0053, 0x0049, 0x004F, 0x004E, 0x0053, 0x0045, 0x0000}; + +static const TransItem pFr[] = { + {cell_address_fr, "ADDRESS", ocCell}, + {cell_col_fr, "COL", ocCell}, + {cell_contents_fr, "CONTENTS", ocCell}, + {cell_color_fr, "COLOR", ocCell}, + {cell_width_fr, "WIDTH", ocCell}, + {cell_row_fr, "ROW", ocCell}, + {cell_filename_fr, "FILENAME", ocCell}, + {cell_prefix_fr, "PREFIX", ocCell}, + {cell_protect_fr, "PROTECT", ocCell}, + {info_numfile_fr, "NUMFILE", ocInfo}, + {info_recalc_fr, "RECALC", ocInfo}, + {info_system_fr, "SYSTEM", ocInfo}, + {info_release_fr, "RELEASE", ocInfo}, + {info_osversion_fr, "OSVERSION", ocInfo}, + {nullptr, nullptr, ocNone} +}; + +addToMap(pFr, aFr); + + +// Hungarian language locale (automatically generated) + +static const lang::Locale aHu("hu", "", ""); + +// pre instantiations of localized function names +static const sal_Unicode cell_address_hu[] = { + 0x0043, 0x00CD, 0x004D, 0x0000}; +static const sal_Unicode cell_col_hu[] = { + 0x004F, 0x0053, 0x005A, 0x004C, 0x004F, 0x0050, 0x0000}; +static const sal_Unicode cell_color_hu[] = { + 0x0053, 0x005A, 0x00CD, 0x004E, 0x0000}; +static const sal_Unicode cell_contents_hu[] = { + 0x0054, 0x0041, 0x0052, 0x0054, 0x0041, 0x004C, 0x004F, 0x004D, 0x0000}; +static const sal_Unicode cell_width_hu[] = { + 0x0053, 0x005A, 0x00C9, 0x004C, 0x0045, 0x0053, 0x0000}; +static const sal_Unicode cell_row_hu[] = { + 0x0053, 0x004F, 0x0052, 0x0000}; +static const sal_Unicode cell_filename_hu[] = { + 0x0046, 0x0049, 0x004C, 0x0045, 0x004E, 0x00C9, 0x0056, 0x0000}; +static const sal_Unicode cell_prefix_hu[] = { + 0x0050, 0x0052, 0x0045, 0x0046, 0x0049, 0x0058, 0x0000}; +static const sal_Unicode cell_protect_hu[] = { + 0x0056, 0x00C9, 0x0044, 0x0045, 0x0054, 0x0054, 0x0000}; +static const sal_Unicode cell_coord_hu[] = { + 0x004B, 0x004F, 0x004F, 0x0052, 0x0044, 0x0000}; +static const sal_Unicode cell_format_hu[] = { + 0x0046, 0x004F, 0x0052, 0x004D, 0x0041, 0x0000}; +static const sal_Unicode cell_parentheses_hu[] = { + 0x005A, 0x00C1, 0x0052, 0x00D3, 0x004A, 0x0045, 0x004C, 0x0045, 0x004B, 0x0000}; +static const sal_Unicode cell_sheet_hu[] = { + 0x004C, 0x0041, 0x0050, 0x0000}; +static const sal_Unicode cell_type_hu[] = { + 0x0054, 0x00CD, 0x0050, 0x0055, 0x0053, 0x0000}; +static const sal_Unicode info_numfile_hu[] = { + 0x0046, 0x0049, 0x004C, 0x0045, 0x0053, 0x005A, 0x00C1, 0x004D, 0x0000}; +static const sal_Unicode info_recalc_hu[] = { + 0x0053, 0x005A, 0x00C1, 0x004D, 0x004F, 0x004C, 0x00C1, 0x0053, 0x0000}; +static const sal_Unicode info_system_hu[] = { + 0x0052, 0x0045, 0x004E, 0x0044, 0x0053, 0x005A, 0x0045, 0x0052, 0x0000}; +static const sal_Unicode info_release_hu[] = { + 0x0056, 0x0045, 0x0052, 0x005A, 0x0049, 0x00D3, 0x0000}; +static const sal_Unicode info_osversion_hu[] = { + 0x004F, 0x0050, 0x0052, 0x0045, 0x004E, 0x0044, 0x0053, 0x005A, 0x0045, 0x0052, 0x0000}; + +static const TransItem pHu[] = { + {cell_address_hu, "ADDRESS", ocCell}, + {cell_col_hu, "COL", ocCell}, + {cell_color_hu, "COLOR", ocCell}, + {cell_contents_hu, "CONTENTS", ocCell}, + {cell_width_hu, "WIDTH", ocCell}, + {cell_row_hu, "ROW", ocCell}, + {cell_filename_hu, "FILENAME", ocCell}, + {cell_prefix_hu, "PREFIX", ocCell}, + {cell_protect_hu, "PROTECT", ocCell}, + {cell_coord_hu, "COORD", ocCell}, + {cell_format_hu, "FORMAT", ocCell}, + {cell_parentheses_hu, "PARENTHESES", ocCell}, + {cell_sheet_hu, "SHEET", ocCell}, + {cell_type_hu, "TYPE", ocCell}, + {info_numfile_hu, "NUMFILE", ocInfo}, + {info_recalc_hu, "RECALC", ocInfo}, + {info_system_hu, "SYSTEM", ocInfo}, + {info_release_hu, "RELEASE", ocInfo}, + {info_osversion_hu, "OSVERSION", ocInfo}, + {nullptr, nullptr, ocNone} +}; + +addToMap(pHu, aHu); + + +// German language locale (automatically generated) + +static const lang::Locale aDe("de", "", ""); + +// pre instantiations of localized function names +static const sal_Unicode cell_row_de[] = { + 0x005A, 0x0045, 0x0049, 0x004C, 0x0045, 0x0000}; +static const sal_Unicode cell_col_de[] = { + 0x0053, 0x0050, 0x0041, 0x004C, 0x0054, 0x0045, 0x0000}; +static const sal_Unicode cell_width_de[] = { + 0x0042, 0x0052, 0x0045, 0x0049, 0x0054, 0x0045, 0x0000}; +static const sal_Unicode cell_address_de[] = { + 0x0041, 0x0044, 0x0052, 0x0045, 0x0053, 0x0053, 0x0045, 0x0000}; +static const sal_Unicode cell_filename_de[] = { + 0x0044, 0x0041, 0x0054, 0x0045, 0x0049, 0x004E, 0x0041, 0x004D, 0x0045, 0x0000}; +static const sal_Unicode cell_color_de[] = { + 0x0046, 0x0041, 0x0052, 0x0042, 0x0045, 0x0000}; +static const sal_Unicode cell_format_de[] = { + 0x0046, 0x004F, 0x0052, 0x004D, 0x0041, 0x0054, 0x0000}; +static const sal_Unicode cell_contents_de[] = { + 0x0049, 0x004E, 0x0048, 0x0041, 0x004C, 0x0054, 0x0000}; +static const sal_Unicode cell_parentheses_de[] = { + 0x004B, 0x004C, 0x0041, 0x004D, 0x004D, 0x0045, 0x0052, 0x004E, 0x0000}; +static const sal_Unicode cell_protect_de[] = { + 0x0053, 0x0043, 0x0048, 0x0055, 0x0054, 0x005A, 0x0000}; +static const sal_Unicode cell_type_de[] = { + 0x0054, 0x0059, 0x0050, 0x0000}; +static const sal_Unicode cell_prefix_de[] = { + 0x0050, 0x0052, 0x00C4, 0x0046, 0x0049, 0x0058, 0x0000}; +static const sal_Unicode cell_sheet_de[] = { + 0x0042, 0x004C, 0x0041, 0x0054, 0x0054, 0x0000}; +static const sal_Unicode cell_coord_de[] = { + 0x004B, 0x004F, 0x004F, 0x0052, 0x0044, 0x0000}; + +static const TransItem pDe[] = { + {cell_row_de, "ROW", ocCell}, + {cell_col_de, "COL", ocCell}, + {cell_width_de, "WIDTH", ocCell}, + {cell_address_de, "ADDRESS", ocCell}, + {cell_filename_de, "FILENAME", ocCell}, + {cell_color_de, "COLOR", ocCell}, + {cell_format_de, "FORMAT", ocCell}, + {cell_contents_de, "CONTENTS", ocCell}, + {cell_parentheses_de, "PARENTHESES", ocCell}, + {cell_protect_de, "PROTECT", ocCell}, + {cell_type_de, "TYPE", ocCell}, + {cell_prefix_de, "PREFIX", ocCell}, + {cell_sheet_de, "SHEET", ocCell}, + {cell_coord_de, "COORD", ocCell}, + {nullptr, nullptr, ocNone} +}; + +addToMap(pDe, aDe); diff --git a/sc/source/core/tool/chartarr.cxx b/sc/source/core/tool/chartarr.cxx new file mode 100644 index 0000000000..58f1b12215 --- /dev/null +++ b/sc/source/core/tool/chartarr.cxx @@ -0,0 +1,374 @@ +/* -*- 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 <float.h> + +#include <chartarr.hxx> +#include <cellvalue.hxx> +#include <document.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <formulacell.hxx> +#include <docoptio.hxx> + +#include <formula/errorcodes.hxx> + +#include <memory> +#include <vector> + +using ::std::vector; + +ScMemChart::ScMemChart(SCCOL nCols, SCROW nRows) +{ + nRowCnt = nRows; + nColCnt = nCols; + pData.reset( new double[nColCnt * nRowCnt] ); + + memset( pData.get(), 0.0, nColCnt * nRowCnt ); + + pColText.reset( new OUString[nColCnt] ); + pRowText.reset( new OUString[nRowCnt] ); +} + +ScMemChart::~ScMemChart() +{ +} + +ScChartArray::ScChartArray( + ScDocument& rDoc, const ScRangeListRef& rRangeList ) : + rDocument( rDoc ), + aPositioner(rDoc, rRangeList) {} + +std::unique_ptr<ScMemChart> ScChartArray::CreateMemChart() +{ + ScRangeListRef aRangeListRef(GetRangeList()); + size_t nCount = aRangeListRef->size(); + if ( nCount > 1 ) + return CreateMemChartMulti(); + else if ( nCount == 1 ) + { + const ScRange & rR = aRangeListRef->front(); + if ( rR.aStart.Tab() != rR.aEnd.Tab() ) + return CreateMemChartMulti(); + else + return CreateMemChartSingle(); + } + else + return CreateMemChartMulti(); // Can handle 0 range better than Single +} + +namespace { + +double getCellValue( ScDocument& rDoc, const ScAddress& rPos, double fDefault, bool bCalcAsShown ) +{ + double fRet = fDefault; + + ScRefCellValue aCell(rDoc, rPos); + switch (aCell.getType()) + { + case CELLTYPE_VALUE: + { + fRet = aCell.getValue(); + if (bCalcAsShown && fRet != 0.0) + { + sal_uInt32 nFormat = rDoc.GetNumberFormat(rPos); + fRet = rDoc.RoundValueAsShown(fRet, nFormat); + } + } + break; + case CELLTYPE_FORMULA: + { + ScFormulaCell* pFCell = aCell.getFormula(); + if (pFCell && pFCell->GetErrCode() == FormulaError::NONE && pFCell->IsValue()) + fRet = pFCell->GetValue(); + } + break; + default: + ; + } + return fRet; +} + +} + +std::unique_ptr<ScMemChart> ScChartArray::CreateMemChartSingle() +{ + SCSIZE nCol; + SCSIZE nRow; + + // real size (without hidden rows/columns) + + SCCOL nColAdd = HasRowHeaders() ? 1 : 0; + SCROW nRowAdd = HasColHeaders() ? 1 : 0; + + SCCOL nCol1; + SCROW nRow1; + SCTAB nTab1; + SCCOL nCol2; + SCROW nRow2; + SCTAB nTab2; + ScRangeListRef aRangeListRef(GetRangeList()); + aRangeListRef->front().GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + + SCCOL nStrCol = nCol1; // remember for labeling + SCROW nStrRow = nRow1; + // Skip hidden columns. + // TODO: make use of last column value once implemented. + SCCOL nLastCol = -1; + while (rDocument.ColHidden(nCol1, nTab1, nullptr, &nLastCol)) + ++nCol1; + + // Skip hidden rows. + SCROW nLastRow = -1; + if (rDocument.RowHidden(nRow1, nTab1, nullptr, &nLastRow)) + nRow1 = nLastRow + 1; + + // if everything is hidden then the label remains at the beginning + if ( nCol1 <= nCol2 ) + { + nStrCol = nCol1; + nCol1 = sal::static_int_cast<SCCOL>( nCol1 + nColAdd ); + } + if ( nRow1 <= nRow2 ) + { + nStrRow = nRow1; + nRow1 = sal::static_int_cast<SCROW>( nRow1 + nRowAdd ); + } + + SCSIZE nTotalCols = ( nCol1 <= nCol2 ? nCol2 - nCol1 + 1 : 0 ); + vector<SCCOL> aCols; + aCols.reserve(nTotalCols); + for (SCSIZE i=0; i<nTotalCols; i++) + { + SCCOL nThisCol = sal::static_int_cast<SCCOL>(nCol1+i); + if (!rDocument.ColHidden(nThisCol, nTab1, nullptr, &nLastCol)) + aCols.push_back(nThisCol); + } + SCSIZE nColCount = aCols.size(); + + SCSIZE nTotalRows = ( nRow1 <= nRow2 ? nRow2 - nRow1 + 1 : 0 ); + vector<SCROW> aRows; + aRows.reserve(nTotalRows); + if (nRow1 <= nRow2) + { + // Get all visible rows between nRow1 and nRow2. + SCROW nThisRow = nRow1; + while (nThisRow <= nRow2) + { + if (rDocument.RowHidden(nThisRow, nTab1, nullptr, &nLastRow)) + nThisRow = nLastRow; + else + aRows.push_back(nThisRow); + ++nThisRow; + } + } + SCSIZE nRowCount = aRows.size(); + + // May happen at least with more than 32k rows. + if (nColCount > SHRT_MAX || nRowCount > SHRT_MAX) + { + nColCount = 0; + nRowCount = 0; + } + + bool bValidData = true; + if ( !nColCount ) + { + bValidData = false; + nColCount = 1; + aCols.push_back(nStrCol); + } + if ( !nRowCount ) + { + bValidData = false; + nRowCount = 1; + aRows.push_back(nStrRow); + } + + // Data + std::unique_ptr<ScMemChart> pMemChart(new ScMemChart( nColCount, nRowCount )); + + if ( bValidData ) + { + bool bCalcAsShown = rDocument.GetDocOptions().IsCalcAsShown(); + for (nCol=0; nCol<nColCount; nCol++) + { + for (nRow=0; nRow<nRowCount; nRow++) + { + // DBL_MIN is a Hack for Chart to recognize empty cells. + ScAddress aPos(aCols[nCol], aRows[nRow], nTab1); + double nVal = getCellValue(rDocument, aPos, DBL_MIN, bCalcAsShown); + pMemChart->SetData(nCol, nRow, nVal); + } + } + } + else + { + // Flag marking data as invalid? + for (nCol=0; nCol<nColCount; nCol++) + for (nRow=0; nRow<nRowCount; nRow++) + pMemChart->SetData( nCol, nRow, DBL_MIN ); + } + + // Column Header + + for (nCol=0; nCol<nColCount; nCol++) + { + OUString aString; + if (HasColHeaders()) + aString = rDocument.GetString(aCols[nCol], nStrRow, nTab1); + if (aString.isEmpty()) + { + ScAddress aPos( aCols[ nCol ], 0, 0 ); + aString = ScResId(STR_COLUMN) + " " + + aPos.Format(ScRefFlags::COL_VALID); + } + pMemChart->SetColText( nCol, aString); + } + + // Row Header + + for (nRow=0; nRow<nRowCount; nRow++) + { + OUString aString; + if (HasRowHeaders()) + { + aString = rDocument.GetString(nStrCol, aRows[nRow], nTab1); + } + if (aString.isEmpty()) + { + aString = ScResId(STR_ROW) + " " + + OUString::number(static_cast<sal_Int32>(aRows[nRow]+1)); + } + pMemChart->SetRowText( nRow, aString); + } + + return pMemChart; +} + +std::unique_ptr<ScMemChart> ScChartArray::CreateMemChartMulti() +{ + SCSIZE nColCount = GetPositionMap()->GetColCount(); + SCSIZE nRowCount = GetPositionMap()->GetRowCount(); + + // May happen at least with more than 32k rows. + if (nColCount > SHRT_MAX || nRowCount > SHRT_MAX) + { + nColCount = 0; + nRowCount = 0; + } + + bool bValidData = true; + if ( !nColCount ) + { + bValidData = false; + nColCount = 1; + } + if ( !nRowCount ) + { + bValidData = false; + nRowCount = 1; + } + + // Data + std::unique_ptr<ScMemChart> pMemChart(new ScMemChart( nColCount, nRowCount )); + + SCSIZE nCol = 0; + SCSIZE nRow = 0; + bool bCalcAsShown = rDocument.GetDocOptions().IsCalcAsShown(); + sal_uLong nIndex = 0; + if (bValidData) + { + for ( nCol = 0; nCol < nColCount; nCol++ ) + { + for ( nRow = 0; nRow < nRowCount; nRow++, nIndex++ ) + { + double nVal = DBL_MIN; // Hack for Chart to recognize empty cells + const ScAddress* pPos = GetPositionMap()->GetPosition( nIndex ); + if (pPos) + // otherwise: Gap + nVal = getCellValue(rDocument, *pPos, DBL_MIN, bCalcAsShown); + + pMemChart->SetData(nCol, nRow, nVal); + } + } + } + else + { + for ( nRow = 0; nRow < nRowCount; nRow++, nIndex++ ) + { + double nVal = DBL_MIN; // Hack for Chart to recognize empty cells + const ScAddress* pPos = GetPositionMap()->GetPosition( nIndex ); + if (pPos) + // otherwise: Gap + nVal = getCellValue(rDocument, *pPos, DBL_MIN, bCalcAsShown); + + pMemChart->SetData(nCol, nRow, nVal); + } + } + + //TODO: Label when gaps + + // Column header + + SCCOL nPosCol = 0; + for ( nCol = 0; nCol < nColCount; nCol++ ) + { + OUString aString; + const ScAddress* pPos = GetPositionMap()->GetColHeaderPosition( static_cast<SCCOL>(nCol) ); + if ( HasColHeaders() && pPos ) + aString = rDocument.GetString(pPos->Col(), pPos->Row(), pPos->Tab()); + + if (aString.isEmpty()) + { + if ( pPos ) + nPosCol = pPos->Col() + 1; + else + nPosCol++; + ScAddress aPos( nPosCol - 1, 0, 0 ); + aString = ScResId(STR_COLUMN) + " " + aPos.Format(ScRefFlags::COL_VALID); + } + pMemChart->SetColText( nCol, aString); + } + + // Row header + + SCROW nPosRow = 0; + for ( nRow = 0; nRow < nRowCount; nRow++ ) + { + OUString aString; + const ScAddress* pPos = GetPositionMap()->GetRowHeaderPosition( nRow ); + if ( HasRowHeaders() && pPos ) + aString = rDocument.GetString(pPos->Col(), pPos->Row(), pPos->Tab()); + + if (aString.isEmpty()) + { + if ( pPos ) + nPosRow = pPos->Row() + 1; + else + nPosRow++; + aString = ScResId(STR_ROW) + " " + OUString::number(static_cast<sal_Int32>(nPosRow)); + } + pMemChart->SetRowText( nRow, aString); + } + + return pMemChart; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/charthelper.cxx b/sc/source/core/tool/charthelper.cxx new file mode 100644 index 0000000000..82e11c0550 --- /dev/null +++ b/sc/source/core/tool/charthelper.cxx @@ -0,0 +1,432 @@ +/* -*- 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 <charthelper.hxx> +#include <document.hxx> +#include <drwlayer.hxx> +#include <rangelst.hxx> +#include <chartlis.hxx> +#include <docuno.hxx> + +#include <comphelper/propertyvalue.hxx> +#include <svx/svditer.hxx> +#include <svx/svdoole2.hxx> +#include <svx/svdpage.hxx> +#include <svtools/embedhlp.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <com/sun/star/chart2/XChartDocument.hpp> +#include <com/sun/star/chart2/data/XDataReceiver.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/util/XModifiable.hpp> + +using namespace com::sun::star; +using ::com::sun::star::uno::Reference; + +namespace +{ + +sal_uInt16 lcl_DoUpdateCharts( ScDocument& rDoc ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return 0; + + sal_uInt16 nFound = 0; + + sal_uInt16 nPageCount = pModel->GetPageCount(); + for (sal_uInt16 nPageNo=0; nPageNo<nPageCount; nPageNo++) + { + SdrPage* pPage = pModel->GetPage(nPageNo); + OSL_ENSURE(pPage,"Page ?"); + + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 && ScDocument::IsChart( pObject ) ) + { + OUString aName = static_cast<SdrOle2Obj*>(pObject)->GetPersistName(); + rDoc.UpdateChart( aName ); + ++nFound; + } + pObject = aIter.Next(); + } + } + return nFound; +} + +bool lcl_AdjustRanges( ScRangeList& rRanges, SCTAB nSourceTab, SCTAB nDestTab, SCTAB nTabCount ) +{ + //TODO: if multiple sheets are copied, update references into the other copied sheets? + + bool bChanged = false; + + for ( size_t i=0, nCount = rRanges.size(); i < nCount; i++ ) + { + ScRange & rRange = rRanges[ i ]; + if ( rRange.aStart.Tab() == nSourceTab && rRange.aEnd.Tab() == nSourceTab ) + { + rRange.aStart.SetTab( nDestTab ); + rRange.aEnd.SetTab( nDestTab ); + bChanged = true; + } + if ( rRange.aStart.Tab() >= nTabCount ) + { + rRange.aStart.SetTab( nTabCount > 0 ? ( nTabCount - 1 ) : 0 ); + bChanged = true; + } + if ( rRange.aEnd.Tab() >= nTabCount ) + { + rRange.aEnd.SetTab( nTabCount > 0 ? ( nTabCount - 1 ) : 0 ); + bChanged = true; + } + } + + return bChanged; +} + +}//end anonymous namespace + +// ScChartHelper +//static +sal_uInt16 ScChartHelper::DoUpdateAllCharts( ScDocument& rDoc ) +{ + return lcl_DoUpdateCharts( rDoc ); +} + +void ScChartHelper::AdjustRangesOfChartsOnDestinationPage( const ScDocument& rSrcDoc, ScDocument& rDestDoc, const SCTAB nSrcTab, const SCTAB nDestTab ) +{ + ScDrawLayer* pDrawLayer = rDestDoc.GetDrawLayer(); + if( !pDrawLayer ) + return; + + SdrPage* pDestPage = pDrawLayer->GetPage(static_cast<sal_uInt16>(nDestTab)); + if( !pDestPage ) + return; + + SdrObjListIter aIter( pDestPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while( pObject ) + { + if( pObject->GetObjIdentifier() == SdrObjKind::OLE2 && static_cast<SdrOle2Obj*>(pObject)->IsChart() ) + { + OUString aChartName = static_cast<SdrOle2Obj*>(pObject)->GetPersistName(); + + Reference< chart2::XChartDocument > xChartDoc( rDestDoc.GetChartByName( aChartName ) ); + Reference< chart2::data::XDataReceiver > xReceiver( xChartDoc, uno::UNO_QUERY ); + if( xChartDoc.is() && xReceiver.is() && !xChartDoc->hasInternalDataProvider() ) + { + ::std::vector< ScRangeList > aRangesVector; + rDestDoc.GetChartRanges( aChartName, aRangesVector, rSrcDoc ); + + for( ScRangeList& rScRangeList : aRangesVector ) + { + lcl_AdjustRanges( rScRangeList, nSrcTab, nDestTab, rDestDoc.GetTableCount() ); + } + rDestDoc.SetChartRanges( aChartName, aRangesVector ); + } + } + pObject = aIter.Next(); + } +} + +void ScChartHelper::UpdateChartsOnDestinationPage( ScDocument& rDestDoc, const SCTAB nDestTab ) +{ + ScDrawLayer* pDrawLayer = rDestDoc.GetDrawLayer(); + if( !pDrawLayer ) + return; + + SdrPage* pDestPage = pDrawLayer->GetPage(static_cast<sal_uInt16>(nDestTab)); + if( !pDestPage ) + return; + + SdrObjListIter aIter( pDestPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while( pObject ) + { + if( pObject->GetObjIdentifier() == SdrObjKind::OLE2 && static_cast<SdrOle2Obj*>(pObject)->IsChart() ) + { + OUString aChartName = static_cast<SdrOle2Obj*>(pObject)->GetPersistName(); + Reference< chart2::XChartDocument > xChartDoc( rDestDoc.GetChartByName( aChartName ) ); + Reference< util::XModifiable > xModif(xChartDoc, uno::UNO_QUERY_THROW); + xModif->setModified( true); + } + pObject = aIter.Next(); + } +} + +uno::Reference< chart2::XChartDocument > ScChartHelper::GetChartFromSdrObject( const SdrObject* pObject ) +{ + uno::Reference< chart2::XChartDocument > xReturn; + if( pObject ) + { + if( pObject->GetObjIdentifier() == SdrObjKind::OLE2 && static_cast<const SdrOle2Obj*>(pObject)->IsChart() ) + { + uno::Reference< embed::XEmbeddedObject > xIPObj = static_cast<const SdrOle2Obj*>(pObject)->GetObjRef(); + if( xIPObj.is() ) + { + (void)svt::EmbeddedObjectRef::TryRunningState( xIPObj ); + uno::Reference< util::XCloseable > xComponent = xIPObj->getComponent(); + xReturn.set( uno::Reference< chart2::XChartDocument >( xComponent, uno::UNO_QUERY ) ); + } + } + } + return xReturn; +} + +void ScChartHelper::GetChartRanges( const uno::Reference< chart2::XChartDocument >& xChartDoc, + std::vector< OUString >& rRanges ) +{ + rRanges.clear(); + uno::Reference< chart2::data::XDataSource > xDataSource( xChartDoc, uno::UNO_QUERY ); + if( !xDataSource.is() ) + return; + + const uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > > aLabeledDataSequences( xDataSource->getDataSequences() ); + rRanges.reserve(2*aLabeledDataSequences.getLength()); + for(const uno::Reference<chart2::data::XLabeledDataSequence>& xLabeledSequence : aLabeledDataSequences) + { + if(!xLabeledSequence.is()) + continue; + uno::Reference< chart2::data::XDataSequence > xLabel( xLabeledSequence->getLabel()); + uno::Reference< chart2::data::XDataSequence > xValues( xLabeledSequence->getValues()); + + if (xLabel.is()) + rRanges.push_back( xLabel->getSourceRangeRepresentation() ); + if (xValues.is()) + rRanges.push_back( xValues->getSourceRangeRepresentation() ); + } +} + +void ScChartHelper::SetChartRanges( const uno::Reference< chart2::XChartDocument >& xChartDoc, + const uno::Sequence< OUString >& rRanges ) +{ + uno::Reference< chart2::data::XDataSource > xDataSource( xChartDoc, uno::UNO_QUERY ); + if( !xDataSource.is() ) + return; + uno::Reference< chart2::data::XDataProvider > xDataProvider = xChartDoc->getDataProvider(); + if( !xDataProvider.is() ) + return; + + xChartDoc->lockControllers(); + + try + { + OUString aPropertyNameRole( "Role" ); + + uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > > aLabeledDataSequences( xDataSource->getDataSequences() ); + sal_Int32 nRange=0; + for( uno::Reference<chart2::data::XLabeledDataSequence>& xLabeledSequence : asNonConstRange(aLabeledDataSequences) ) + { + if( nRange >= rRanges.getLength() ) + break; + + if(!xLabeledSequence.is()) + continue; + uno::Reference< beans::XPropertySet > xLabel( xLabeledSequence->getLabel(), uno::UNO_QUERY ); + uno::Reference< beans::XPropertySet > xValues( xLabeledSequence->getValues(), uno::UNO_QUERY ); + + if( xLabel.is()) + { + uno::Reference< chart2::data::XDataSequence > xNewSeq( + xDataProvider->createDataSequenceByRangeRepresentation( rRanges[nRange++] )); + + uno::Reference< beans::XPropertySet > xNewProps( xNewSeq, uno::UNO_QUERY ); + if( xNewProps.is() ) + xNewProps->setPropertyValue( aPropertyNameRole, xLabel->getPropertyValue( aPropertyNameRole ) ); + + xLabeledSequence->setLabel( xNewSeq ); + } + + if( nRange >= rRanges.getLength() ) + break; + + if( xValues.is()) + { + uno::Reference< chart2::data::XDataSequence > xNewSeq( + xDataProvider->createDataSequenceByRangeRepresentation( rRanges[nRange++] )); + + uno::Reference< beans::XPropertySet > xNewProps( xNewSeq, uno::UNO_QUERY ); + if( xNewProps.is() ) + xNewProps->setPropertyValue( aPropertyNameRole, xValues->getPropertyValue( aPropertyNameRole ) ); + + xLabeledSequence->setValues( xNewSeq ); + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "sc", "Exception in ScChartHelper::SetChartRanges - invalid range string?"); + } + + xChartDoc->unlockControllers(); +} + +void ScChartHelper::AddRangesIfProtectedChart( ScRangeListVector& rRangesVector, const ScDocument& rDocument, SdrObject* pObject ) +{ + if ( !(pObject && ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 )) ) + return; + + SdrOle2Obj* pSdrOle2Obj = dynamic_cast< SdrOle2Obj* >( pObject ); + if ( !(pSdrOle2Obj && pSdrOle2Obj->IsChart()) ) + return; + + const uno::Reference< embed::XEmbeddedObject >& xEmbeddedObj = pSdrOle2Obj->GetObjRef(); + if ( !xEmbeddedObj.is() ) + return; + + bool bDisableDataTableDialog = false; + sal_Int32 nOldState = xEmbeddedObj->getCurrentState(); + (void)svt::EmbeddedObjectRef::TryRunningState( xEmbeddedObj ); + uno::Reference< beans::XPropertySet > xProps( xEmbeddedObj->getComponent(), uno::UNO_QUERY ); + if ( xProps.is() && + ( xProps->getPropertyValue("DisableDataTableDialog") >>= bDisableDataTableDialog ) && + bDisableDataTableDialog ) + { + ScChartListenerCollection* pCollection = rDocument.GetChartListenerCollection(); + if (pCollection) + { + const OUString& aChartName = pSdrOle2Obj->GetPersistName(); + const ScChartListener* pListener = pCollection->findByName(aChartName); + if (pListener) + { + const ScRangeListRef& rRangeList = pListener->GetRangeList(); + if ( rRangeList.is() ) + { + rRangesVector.push_back( *rRangeList ); + } + } + } + } + if ( xEmbeddedObj->getCurrentState() != nOldState ) + { + xEmbeddedObj->changeState( nOldState ); + } +} + +void ScChartHelper::FillProtectedChartRangesVector( ScRangeListVector& rRangesVector, const ScDocument& rDocument, const SdrPage* pPage ) +{ + if ( pPage ) + { + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while ( pObject ) + { + AddRangesIfProtectedChart( rRangesVector, rDocument, pObject ); + pObject = aIter.Next(); + } + } +} + +void ScChartHelper::GetChartNames( ::std::vector< OUString >& rChartNames, const SdrPage* pPage ) +{ + if ( !pPage ) + return; + + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while ( pObject ) + { + if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 ) + { + SdrOle2Obj* pSdrOle2Obj = dynamic_cast< SdrOle2Obj* >( pObject ); + if ( pSdrOle2Obj && pSdrOle2Obj->IsChart() ) + { + rChartNames.push_back( pSdrOle2Obj->GetPersistName() ); + } + } + pObject = aIter.Next(); + } +} + +void ScChartHelper::CreateProtectedChartListenersAndNotify( ScDocument& rDoc, const SdrPage* pPage, ScModelObj* pModelObj, SCTAB nTab, + const ScRangeListVector& rRangesVector, const ::std::vector< OUString >& rExcludedChartNames, bool bSameDoc ) +{ + if ( !(pPage && pModelObj) ) + return; + + size_t nRangeListCount = rRangesVector.size(); + size_t nRangeList = 0; + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while ( pObject ) + { + if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 ) + { + SdrOle2Obj* pSdrOle2Obj = dynamic_cast< SdrOle2Obj* >( pObject ); + if ( pSdrOle2Obj && pSdrOle2Obj->IsChart() ) + { + const OUString& aChartName = pSdrOle2Obj->GetPersistName(); + ::std::vector< OUString >::const_iterator aEnd = rExcludedChartNames.end(); + ::std::vector< OUString >::const_iterator aFound = ::std::find( rExcludedChartNames.begin(), aEnd, aChartName ); + if ( aFound == aEnd ) + { + const uno::Reference< embed::XEmbeddedObject >& xEmbeddedObj = pSdrOle2Obj->GetObjRef(); + if ( xEmbeddedObj.is() && ( nRangeList < nRangeListCount ) ) + { + bool bDisableDataTableDialog = false; + (void)svt::EmbeddedObjectRef::TryRunningState( xEmbeddedObj ); + uno::Reference< beans::XPropertySet > xProps( xEmbeddedObj->getComponent(), uno::UNO_QUERY ); + if ( xProps.is() && + ( xProps->getPropertyValue("DisableDataTableDialog") >>= bDisableDataTableDialog ) && + bDisableDataTableDialog ) + { + if ( bSameDoc ) + { + ScChartListenerCollection* pCollection = rDoc.GetChartListenerCollection(); + if (pCollection && !pCollection->findByName(aChartName)) + { + ScRangeList aRangeList( rRangesVector[ nRangeList++ ] ); + ScRangeListRef rRangeList( new ScRangeList( aRangeList ) ); + ScChartListener* pChartListener = new ScChartListener( aChartName, rDoc, rRangeList ); + pCollection->insert( pChartListener ); + pChartListener->StartListeningTo(); + } + } + else + { + xProps->setPropertyValue("DisableDataTableDialog", + uno::Any( false ) ); + xProps->setPropertyValue("DisableComplexChartTypes", + uno::Any( false ) ); + } + } + } + + if (pModelObj->HasChangesListeners()) + { + tools::Rectangle aRectangle = pSdrOle2Obj->GetSnapRect(); + ScRange aRange( rDoc.GetRange( nTab, aRectangle ) ); + ScRangeList aChangeRanges( aRange ); + + uno::Sequence< beans::PropertyValue > aProperties{ + comphelper::makePropertyValue("Name", aChartName) + }; + + pModelObj->NotifyChanges( "insert-chart", aChangeRanges, aProperties ); + } + } + } + } + pObject = aIter.Next(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/chartlis.cxx b/sc/source/core/tool/chartlis.cxx new file mode 100644 index 0000000000..024ff205e1 --- /dev/null +++ b/sc/source/core/tool/chartlis.cxx @@ -0,0 +1,630 @@ +/* -*- 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 <memory> +#include <utility> +#include <vcl/svapp.hxx> + +#include <chartlis.hxx> +#include <brdcst.hxx> +#include <document.hxx> +#include <reftokenhelper.hxx> +#include <formula/token.hxx> +#include <com/sun/star/chart/XChartDataChangeEventListener.hpp> + +using namespace com::sun::star; +using ::std::vector; +using ::std::for_each; + +// Update chart listeners quickly, to get a similar behavior to loaded charts +// which register UNO listeners. + +class ScChartUnoData +{ + uno::Reference< chart::XChartDataChangeEventListener > xListener; + uno::Reference< chart::XChartData > xSource; + +public: + ScChartUnoData( uno::Reference< chart::XChartDataChangeEventListener > xL, + uno::Reference< chart::XChartData > xS ) : + xListener(std::move( xL )), xSource(std::move( xS )) {} + + const uno::Reference< chart::XChartDataChangeEventListener >& GetListener() const { return xListener; } + const uno::Reference< chart::XChartData >& GetSource() const { return xSource; } +}; + +// ScChartListener +ScChartListener::ExternalRefListener::ExternalRefListener(ScChartListener& rParent, ScDocument& rDoc) : + mrParent(rParent), m_pDoc(&rDoc) +{ +} + +ScChartListener::ExternalRefListener::~ExternalRefListener() +{ + if (!m_pDoc || m_pDoc->IsInDtorClear()) + // The document is being destroyed. Do nothing. + return; + + // Make sure to remove all pointers to this object. + m_pDoc->GetExternalRefManager()->removeLinkListener(this); +} + +void ScChartListener::ExternalRefListener::notify(sal_uInt16 nFileId, ScExternalRefManager::LinkUpdateType eType) +{ + switch (eType) + { + case ScExternalRefManager::LINK_MODIFIED: + { + if (maFileIds.count(nFileId)) + // We are listening to this external document. Send an update + // request to the chart. + mrParent.SetUpdateQueue(); + } + break; + case ScExternalRefManager::LINK_BROKEN: + removeFileId(nFileId); + break; + case ScExternalRefManager::OH_NO_WE_ARE_GOING_TO_DIE: + m_pDoc = nullptr; + break; + } +} + +void ScChartListener::ExternalRefListener::addFileId(sal_uInt16 nFileId) +{ + maFileIds.insert(nFileId); +} + +void ScChartListener::ExternalRefListener::removeFileId(sal_uInt16 nFileId) +{ + maFileIds.erase(nFileId); +} + +ScChartListener::ScChartListener( OUString aName, ScDocument& rDocP, + const ScRangeListRef& rRangeList ) : + maName(std::move(aName)), + mrDoc( rDocP ), + bUsed( false ), + bDirty( false ) +{ + ScRefTokenHelper::getTokensFromRangeList(&rDocP, maTokens, *rRangeList); +} + +ScChartListener::ScChartListener( OUString aName, ScDocument& rDocP, vector<ScTokenRef> aTokens ) : + maTokens(std::move(aTokens)), + maName(std::move(aName)), + mrDoc( rDocP ), + bUsed( false ), + bDirty( false ) +{ +} + +ScChartListener::~ScChartListener() +{ + if ( HasBroadcaster() ) + EndListeningTo(); + pUnoData.reset(); + + if (mpExtRefListener) + { + // Stop listening to all external files. + ScExternalRefManager* pRefMgr = mrDoc.GetExternalRefManager(); + const std::unordered_set<sal_uInt16>& rFileIds = mpExtRefListener->getAllFileIds(); + for (const auto& rFileId : rFileIds) + pRefMgr->removeLinkListener(rFileId, mpExtRefListener.get()); + } +} + +void ScChartListener::SetUno( + const uno::Reference< chart::XChartDataChangeEventListener >& rListener, + const uno::Reference< chart::XChartData >& rSource ) +{ + pUnoData.reset( new ScChartUnoData( rListener, rSource ) ); +} + +uno::Reference< chart::XChartDataChangeEventListener > ScChartListener::GetUnoListener() const +{ + if ( pUnoData ) + return pUnoData->GetListener(); + return uno::Reference< chart::XChartDataChangeEventListener >(); +} + +uno::Reference< chart::XChartData > ScChartListener::GetUnoSource() const +{ + if ( pUnoData ) + return pUnoData->GetSource(); + return uno::Reference< chart::XChartData >(); +} + +void ScChartListener::Notify( const SfxHint& rHint ) +{ + if (rHint.GetId() == SfxHintId::ScDataChanged) + SetUpdateQueue(); +} + +void ScChartListener::Update() +{ + if ( mrDoc.IsInInterpreter() ) + { // If interpreting do nothing and restart timer so we don't + // interfere with interpreter and don't produce an Err522 or similar. + // This may happen if we are rescheduled via Basic function. + mrDoc.GetChartListenerCollection()->StartTimer(); + return ; + } + if ( pUnoData ) + { + bDirty = false; + // recognize some day what has changed inside the Chart + chart::ChartDataChangeEvent aEvent( pUnoData->GetSource(), + chart::ChartDataChangeType_ALL, + 0, 0, 0, 0 ); + pUnoData->GetListener()->chartDataChanged( aEvent ); + } + else if ( mrDoc.GetAutoCalc() ) + { + bDirty = false; + mrDoc.UpdateChart(GetName()); + } +} + +ScRangeListRef ScChartListener::GetRangeList() const +{ + ScRangeListRef aRLRef(new ScRangeList); + ScRefTokenHelper::getRangeListFromTokens(&mrDoc, *aRLRef, maTokens, ScAddress()); + return aRLRef; +} + +void ScChartListener::SetRangeList( const ScRangeListRef& rNew ) +{ + vector<ScTokenRef> aTokens; + ScRefTokenHelper::getTokensFromRangeList(&mrDoc, aTokens, *rNew); + maTokens.swap(aTokens); +} + +namespace { + +class StartEndListening +{ +public: + StartEndListening(ScDocument& rDoc, ScChartListener& rParent, bool bStart) : + mrDoc(rDoc), mrParent(rParent), mbStart(bStart) {} + + void operator() (const ScTokenRef& pToken) + { + if (!ScRefTokenHelper::isRef(pToken)) + return; + + bool bExternal = ScRefTokenHelper::isExternalRef(pToken); + if (bExternal) + { + sal_uInt16 nFileId = pToken->GetIndex(); + ScExternalRefManager* pRefMgr = mrDoc.GetExternalRefManager(); + ScChartListener::ExternalRefListener* pExtRefListener = mrParent.GetExtRefListener(); + if (mbStart) + { + pRefMgr->addLinkListener(nFileId, pExtRefListener); + pExtRefListener->addFileId(nFileId); + } + else + { + pRefMgr->removeLinkListener(nFileId, pExtRefListener); + pExtRefListener->removeFileId(nFileId); + } + } + else + { + ScRange aRange; + ScRefTokenHelper::getRangeFromToken(&mrDoc, aRange, pToken, ScAddress(), bExternal); + if (mbStart) + startListening(aRange); + else + endListening(aRange); + } + } +private: + void startListening(const ScRange& rRange) + { + if (rRange.aStart == rRange.aEnd) + mrDoc.StartListeningCell(rRange.aStart, &mrParent); + else + mrDoc.StartListeningArea(rRange, false, &mrParent); + } + + void endListening(const ScRange& rRange) + { + if (rRange.aStart == rRange.aEnd) + mrDoc.EndListeningCell(rRange.aStart, &mrParent); + else + mrDoc.EndListeningArea(rRange, false, &mrParent); + } +private: + ScDocument& mrDoc; + ScChartListener& mrParent; + bool mbStart; +}; + +} + +void ScChartListener::StartListeningTo() +{ + if (maTokens.empty()) + // no references to listen to. + return; + + for_each(maTokens.begin(), maTokens.end(), StartEndListening(mrDoc, *this, true)); +} + +void ScChartListener::EndListeningTo() +{ + if (maTokens.empty()) + // no references to listen to. + return; + + for_each(maTokens.begin(), maTokens.end(), StartEndListening(mrDoc, *this, false)); +} + +void ScChartListener::ChangeListening( const ScRangeListRef& rRangeListRef, + bool bDirtyP ) +{ + EndListeningTo(); + SetRangeList( rRangeListRef ); + StartListeningTo(); + if ( bDirtyP ) + SetDirty( true ); +} + +void ScChartListener::UpdateChartIntersecting( const ScRange& rRange ) +{ + ScTokenRef pToken; + ScRefTokenHelper::getTokenFromRange(&mrDoc, pToken, rRange); + + if (ScRefTokenHelper::intersects(&mrDoc, maTokens, pToken, ScAddress())) + { + // force update (chart has to be loaded), don't use ScChartListener::Update + mrDoc.UpdateChart(GetName()); + } +} + +ScChartListener::ExternalRefListener* ScChartListener::GetExtRefListener() +{ + if (!mpExtRefListener) + mpExtRefListener.reset(new ExternalRefListener(*this, mrDoc)); + + return mpExtRefListener.get(); +} + +void ScChartListener::SetUpdateQueue() +{ + bDirty = true; + mrDoc.GetChartListenerCollection()->StartTimer(); +} + +bool ScChartListener::operator==( const ScChartListener& r ) const +{ + bool b1 = !maTokens.empty(); + bool b2 = !r.maTokens.empty(); + + if (&mrDoc != &r.mrDoc || bUsed != r.bUsed || bDirty != r.bDirty || + GetName() != r.GetName() || b1 != b2) + return false; + + if (!b1 && !b2) + // both token list instances are empty. + return true; + + return maTokens == r.maTokens; +} + +bool ScChartListener::operator!=( const ScChartListener& r ) const +{ + return !operator==(r); +} + +ScChartHiddenRangeListener::ScChartHiddenRangeListener() +{ +} + +ScChartHiddenRangeListener::~ScChartHiddenRangeListener() +{ + // empty d'tor +} + +void ScChartListenerCollection::Init() +{ + aIdle.SetInvokeHandler( LINK( this, ScChartListenerCollection, TimerHdl ) ); + aIdle.SetPriority( TaskPriority::REPAINT ); +} + +ScChartListenerCollection::ScChartListenerCollection( ScDocument& rDocP ) : + meModifiedDuringUpdate( SC_CLCUPDATE_NONE ), + aIdle( "sc::ScChartListenerCollection aIdle" ), + rDoc( rDocP ) +{ + Init(); +} + +ScChartListenerCollection::ScChartListenerCollection( + const ScChartListenerCollection& rColl ) : + meModifiedDuringUpdate( SC_CLCUPDATE_NONE ), + aIdle( "sc::ScChartListenerCollection aIdle" ), + rDoc( rColl.rDoc ) +{ + Init(); +} + +ScChartListenerCollection::~ScChartListenerCollection() +{ + // remove ChartListener objects before aIdle dtor is called, because + // ScChartListener::EndListeningTo may cause ScChartListenerCollection::StartTimer + // to be called if an empty ScNoteCell is deleted + + m_Listeners.clear(); +} + +void ScChartListenerCollection::StartAllListeners() +{ + for (auto const& it : m_Listeners) + { + it.second->StartListeningTo(); + } +} + +bool ScChartListenerCollection::insert(ScChartListener* pListener) +{ + if (meModifiedDuringUpdate == SC_CLCUPDATE_RUNNING) + meModifiedDuringUpdate = SC_CLCUPDATE_MODIFIED; + OUString aName = pListener->GetName(); + return m_Listeners.insert(std::make_pair(aName, std::unique_ptr<ScChartListener>(pListener))).second; +} + +void ScChartListenerCollection::removeByName(const OUString& rName) +{ + if (meModifiedDuringUpdate == SC_CLCUPDATE_RUNNING) + meModifiedDuringUpdate = SC_CLCUPDATE_MODIFIED; + m_Listeners.erase(rName); +} + +ScChartListener* ScChartListenerCollection::findByName(const OUString& rName) +{ + ListenersType::iterator const it = m_Listeners.find(rName); + return it == m_Listeners.end() ? nullptr : it->second.get(); +} + +const ScChartListener* ScChartListenerCollection::findByName(const OUString& rName) const +{ + ListenersType::const_iterator const it = m_Listeners.find(rName); + return it == m_Listeners.end() ? nullptr : it->second.get(); +} + +bool ScChartListenerCollection::hasListeners() const +{ + return !m_Listeners.empty(); +} + +OUString ScChartListenerCollection::getUniqueName(std::u16string_view rPrefix) const +{ + for (sal_Int32 nNum = 1; nNum < 10000; ++nNum) // arbitrary limit to prevent infinite loop. + { + OUString aTestName = rPrefix + OUString::number(nNum); + if (m_Listeners.find(aTestName) == m_Listeners.end()) + return aTestName; + } + return OUString(); +} + +void ScChartListenerCollection::ChangeListening( const OUString& rName, + const ScRangeListRef& rRangeListRef ) +{ + ScChartListener* pCL = findByName(rName); + if (pCL) + { + pCL->EndListeningTo(); + pCL->SetRangeList( rRangeListRef ); + } + else + { + pCL = new ScChartListener(rName, rDoc, rRangeListRef); + insert(pCL); + } + pCL->StartListeningTo(); +} + +void ScChartListenerCollection::FreeUnused() +{ + if (meModifiedDuringUpdate == SC_CLCUPDATE_RUNNING) + meModifiedDuringUpdate = SC_CLCUPDATE_MODIFIED; + + ListenersType aUsed; + + for (auto & pair : m_Listeners) + { + ScChartListener* p = pair.second.get(); + if (p->IsUno()) + { + // We don't delete UNO charts; they are to be deleted separately via FreeUno(). + aUsed.insert(std::make_pair(pair.first, std::move(pair.second))); + continue; + } + + if (p->IsUsed()) + { + p->SetUsed(false); + aUsed.insert(std::make_pair(pair.first, std::move(pair.second))); + } + } + + m_Listeners = std::move(aUsed); +} + +void ScChartListenerCollection::FreeUno( const uno::Reference< chart::XChartDataChangeEventListener >& rListener, + const uno::Reference< chart::XChartData >& rSource ) +{ + if (meModifiedDuringUpdate == SC_CLCUPDATE_RUNNING) + meModifiedDuringUpdate = SC_CLCUPDATE_MODIFIED; + + for (auto it = m_Listeners.begin(); it != m_Listeners.end(); ) + { + ScChartListener *const p = it->second.get(); + if (p->IsUno() && p->GetUnoListener() == rListener && p->GetUnoSource() == rSource) + it = m_Listeners.erase(it); + else + ++it; + } +} + +void ScChartListenerCollection::StartTimer() +{ + aIdle.Start(); +} + +IMPL_LINK_NOARG(ScChartListenerCollection, TimerHdl, Timer *, void) +{ + if ( Application::AnyInput( VclInputFlags::KEYBOARD ) ) + { + aIdle.Start(); + return; + } + UpdateDirtyCharts(); +} + +void ScChartListenerCollection::UpdateDirtyCharts() +{ + // During ScChartListener::Update() the most nasty things can happen due to + // UNO listeners, e.g. reentrant calls via BASIC to insert() and FreeUno() + // and similar that modify m_Listeners and invalidate iterators. + meModifiedDuringUpdate = SC_CLCUPDATE_RUNNING; + + for (auto const& it : m_Listeners) + { + ScChartListener *const p = it.second.get(); + if (p->IsDirty()) + p->Update(); + + if (meModifiedDuringUpdate == SC_CLCUPDATE_MODIFIED) + break; // iterator is invalid + + if (aIdle.IsActive() && !rDoc.IsImportingXML()) + break; // one interfered + } + meModifiedDuringUpdate = SC_CLCUPDATE_NONE; +} + +void ScChartListenerCollection::SetDirty() +{ + for (auto const& it : m_Listeners) + { + it.second->SetDirty(true); + } + + StartTimer(); +} + +void ScChartListenerCollection::SetDiffDirty( + const ScChartListenerCollection& rCmp, bool bSetChartRangeLists ) +{ + bool bDirty = false; + for (auto const& it : m_Listeners) + { + ScChartListener *const pCL = it.second.get(); + assert(pCL); + const ScChartListener* pCLCmp = rCmp.findByName(pCL->GetName()); + if (!pCLCmp || *pCL != *pCLCmp) + { + if ( bSetChartRangeLists ) + { + if (pCLCmp) + { + const ScRangeListRef& rList1 = pCL->GetRangeList(); + const ScRangeListRef& rList2 = pCLCmp->GetRangeList(); + bool b1 = rList1.is(); + bool b2 = rList2.is(); + if ( b1 != b2 || (b1 && b2 && (*rList1 != *rList2)) ) + rDoc.SetChartRangeList( pCL->GetName(), rList1 ); + } + else + rDoc.SetChartRangeList( pCL->GetName(), pCL->GetRangeList() ); + } + bDirty = true; + pCL->SetDirty( true ); + } + } + if ( bDirty ) + StartTimer(); +} + +void ScChartListenerCollection::SetRangeDirty( const ScRange& rRange ) +{ + bool bDirty = false; + for (auto const& it : m_Listeners) + { + ScChartListener *const pCL = it.second.get(); + const ScRangeListRef& rList = pCL->GetRangeList(); + if ( rList.is() && rList->Intersects( rRange ) ) + { + bDirty = true; + pCL->SetDirty( true ); + } + } + if ( bDirty ) + StartTimer(); + + // New hidden range listener implementation + for (auto& [pListener, rHiddenRange] : maHiddenListeners) + { + if (rHiddenRange.Intersects(rRange)) + { + pListener->notify(); + } + } +} + +void ScChartListenerCollection::UpdateChartsContainingTab( SCTAB nTab ) +{ + ScRange aRange( 0, 0, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab ); + for (auto const& it : m_Listeners) + { + it.second->UpdateChartIntersecting(aRange); + } +} + +bool ScChartListenerCollection::operator==( const ScChartListenerCollection& r ) const +{ + // Do not use ScStrCollection::operator==() here that uses IsEqual and Compare. + // Use ScChartListener::operator==() instead. + if (&rDoc != &r.rDoc) + return false; + + return std::equal(m_Listeners.begin(), m_Listeners.end(), r.m_Listeners.begin(), r.m_Listeners.end(), + [](const ListenersType::value_type& lhs, const ListenersType::value_type& rhs) { + return (lhs.first == rhs.first) && (*lhs.second == *rhs.second); + }); +} + +void ScChartListenerCollection::StartListeningHiddenRange( const ScRange& rRange, ScChartHiddenRangeListener* pListener ) +{ + maHiddenListeners.insert(std::make_pair<>(pListener, rRange)); +} + +void ScChartListenerCollection::EndListeningHiddenRange( ScChartHiddenRangeListener* pListener ) +{ + auto range = maHiddenListeners.equal_range(pListener); + maHiddenListeners.erase(range.first, range.second); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/chartlock.cxx b/sc/source/core/tool/chartlock.cxx new file mode 100644 index 0000000000..6b55b0bf7a --- /dev/null +++ b/sc/source/core/tool/chartlock.cxx @@ -0,0 +1,180 @@ +/* -*- 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 <svx/svditer.hxx> +#include <svx/svdoole2.hxx> +#include <svx/svdpage.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <chartlock.hxx> +#include <document.hxx> +#include <drwlayer.hxx> + +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/embed/XComponentSupplier.hpp> +#include <com/sun/star/frame/XModel.hpp> + +using namespace com::sun::star; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::WeakReference; + +#define SC_CHARTLOCKTIMEOUT 660 + +namespace +{ + +std::vector< WeakReference< frame::XModel > > lcl_getAllLivingCharts( ScDocument* pDoc ) +{ + std::vector< WeakReference< frame::XModel > > aRet; + if( !pDoc ) + return aRet; + ScDrawLayer* pDrawLayer = pDoc->GetDrawLayer(); + if (!pDrawLayer) + return aRet; + + for (SCTAB nTab=0; nTab<=pDoc->GetMaxTableNumber(); nTab++) + { + if (pDoc->HasTable(nTab)) + { + SdrPage* pPage = pDrawLayer->GetPage(static_cast<sal_uInt16>(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if( ScDocument::IsChart( pObject ) ) + { + uno::Reference< embed::XEmbeddedObject > xIPObj = static_cast<SdrOle2Obj*>(pObject)->GetObjRef(); + uno::Reference< embed::XComponentSupplier > xCompSupp = xIPObj; + if( xCompSupp.is()) + { + Reference< frame::XModel > xModel( xCompSupp->getComponent(), uno::UNO_QUERY ); + if( xModel.is() ) + aRet.emplace_back(xModel ); + } + } + pObject = aIter.Next(); + } + } + } + return aRet; +} + +}//end anonymous namespace + +// ScChartLockGuard +ScChartLockGuard::ScChartLockGuard( ScDocument* pDoc ) : + maChartModels( lcl_getAllLivingCharts( pDoc ) ) +{ + for( const auto& rxChartModel : maChartModels ) + { + try + { + Reference< frame::XModel > xModel( rxChartModel ); + if( xModel.is()) + xModel->lockControllers(); + } + catch ( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "sc", "Unexpected exception in ScChartLockGuard"); + } + } +} + +ScChartLockGuard::~ScChartLockGuard() +{ + for( const auto& rxChartModel : maChartModels ) + { + try + { + Reference< frame::XModel > xModel( rxChartModel ); + if( xModel.is()) + xModel->unlockControllers(); + } + catch ( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "sc", "Unexpected exception in ScChartLockGuard"); + } + } +} + +void ScChartLockGuard::AlsoLockThisChart( const Reference< frame::XModel >& xModel ) +{ + if(!xModel.is()) + return; + + WeakReference< frame::XModel > xWeakModel(xModel); + + std::vector< WeakReference< frame::XModel > >::iterator aFindIter( + ::std::find( maChartModels.begin(), maChartModels.end(), xWeakModel ) ); + + if( aFindIter == maChartModels.end() ) + { + try + { + xModel->lockControllers(); + maChartModels.emplace_back(xModel ); + } + catch ( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "sc", "Unexpected exception in ScChartLockGuard"); + } + } +} + +// ScTemporaryChartLock +ScTemporaryChartLock::ScTemporaryChartLock( ScDocument* pDocP ) : + mpDoc( pDocP ), maTimer("ScTemporaryChartLock maTimer") +{ + maTimer.SetTimeout( SC_CHARTLOCKTIMEOUT ); + maTimer.SetInvokeHandler( LINK( this, ScTemporaryChartLock, TimeoutHdl ) ); +} + +ScTemporaryChartLock::~ScTemporaryChartLock() +{ + mpDoc = nullptr; + StopLocking(); +} + +void ScTemporaryChartLock::StartOrContinueLocking() +{ + if (!mapScChartLockGuard) + mapScChartLockGuard.reset( new ScChartLockGuard(mpDoc) ); + maTimer.Start(); +} + +void ScTemporaryChartLock::StopLocking() +{ + maTimer.Stop(); + mapScChartLockGuard.reset(); +} + +void ScTemporaryChartLock::AlsoLockThisChart( const Reference< frame::XModel >& xModel ) +{ + if (mapScChartLockGuard) + mapScChartLockGuard->AlsoLockThisChart( xModel ); +} + +IMPL_LINK_NOARG(ScTemporaryChartLock, TimeoutHdl, Timer *, void) +{ + mapScChartLockGuard.reset(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/chartpos.cxx b/sc/source/core/tool/chartpos.cxx new file mode 100644 index 0000000000..11af92f946 --- /dev/null +++ b/sc/source/core/tool/chartpos.cxx @@ -0,0 +1,524 @@ +/* -*- 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 <chartpos.hxx> +#include <document.hxx> +#include <osl/diagnose.h> +#include <svl/numformat.hxx> + +#include <memory> +#include <utility> + +namespace +{ + bool lcl_hasValueDataButNoDates( const ScDocument& rDocument, SCCOL nCol, SCROW nRow, SCTAB nTab ) + { + bool bReturn = false; + if (rDocument.HasValueData( nCol, nRow, nTab )) + { + //treat dates like text #i25706# + sal_uInt32 nNumberFormat = rDocument.GetNumberFormat( ScAddress( nCol, nRow, nTab ) ); + SvNumFormatType nType = rDocument.GetFormatTable()->GetType(nNumberFormat); + bool bIsDate(nType & SvNumFormatType::DATE); + bReturn = !bIsDate; + } + return bReturn; + } +} + +ScChartPositioner::ScChartPositioner( ScDocument& rDoc, SCTAB nTab, + SCCOL nStartColP, SCROW nStartRowP, SCCOL nEndColP, SCROW nEndRowP) : + rDocument( rDoc ), + eGlue( ScChartGlue::NA ), + nStartCol(0), + nStartRow(0), + bColHeaders( false ), + bRowHeaders( false ), + bDummyUpperLeft( false ) +{ + SetRangeList( ScRange( nStartColP, nStartRowP, nTab, nEndColP, nEndRowP, nTab ) ); + CheckColRowHeaders(); +} + +ScChartPositioner::ScChartPositioner( ScDocument& rDoc, ScRangeListRef xRangeList ) : + aRangeListRef(std::move( xRangeList )), + rDocument( rDoc ), + eGlue( ScChartGlue::NA ), + nStartCol(0), + nStartRow(0), + bColHeaders( false ), + bRowHeaders( false ), + bDummyUpperLeft( false ) +{ + if ( aRangeListRef.is() ) + CheckColRowHeaders(); +} + +ScChartPositioner::ScChartPositioner( const ScChartPositioner& rPositioner ) : + aRangeListRef( rPositioner.aRangeListRef ), + rDocument(rPositioner.rDocument), + eGlue(rPositioner.eGlue), + nStartCol(rPositioner.nStartCol), + nStartRow(rPositioner.nStartRow), + bColHeaders(rPositioner.bColHeaders), + bRowHeaders(rPositioner.bRowHeaders), + bDummyUpperLeft( rPositioner.bDummyUpperLeft ) +{ +} + +ScChartPositioner::~ScChartPositioner() +{ +} + +void ScChartPositioner::SetRangeList( const ScRange& rRange ) +{ + aRangeListRef = new ScRangeList( rRange ); + InvalidateGlue(); +} + +void ScChartPositioner::GlueState() +{ + if ( eGlue != ScChartGlue::NA ) + return; + bDummyUpperLeft = false; + ScRange* pR; + if ( aRangeListRef->size() <= 1 ) + { + if ( !aRangeListRef->empty() ) + { + pR = &aRangeListRef->front(); + if ( pR->aStart.Tab() == pR->aEnd.Tab() ) + eGlue = ScChartGlue::NONE; + else + eGlue = ScChartGlue::Cols; // several tables column by column + nStartCol = pR->aStart.Col(); + nStartRow = pR->aStart.Row(); + } + else + { + InvalidateGlue(); + nStartCol = 0; + nStartRow = 0; + } + return; + } + + pR = &aRangeListRef->front(); + nStartCol = pR->aStart.Col(); + nStartRow = pR->aStart.Row(); + SCCOL nMaxCols, nEndCol; + SCROW nMaxRows, nEndRow; + nMaxCols = nEndCol = 0; + nMaxRows = nEndRow = 0; + + // <= so 1 extra pass after last item + for ( size_t i = 1, nRanges = aRangeListRef->size(); i <= nRanges; ++i ) + { // detect spanning/surrounding area etc. + SCCOLROW nTmp, n1, n2; + if ( (n1 = pR->aStart.Col()) < nStartCol ) nStartCol = static_cast<SCCOL>(n1 ); + if ( (n2 = pR->aEnd.Col() ) > nEndCol ) nEndCol = static_cast<SCCOL>(n2 ); + if ( (nTmp = n2 - n1 + 1 ) > nMaxCols ) nMaxCols = static_cast<SCCOL>(nTmp); + if ( (n1 = pR->aStart.Row()) < nStartRow ) nStartRow = static_cast<SCROW>(n1 ); + if ( (n2 = pR->aEnd.Row() ) > nEndRow ) nEndRow = static_cast<SCROW>(n2 ); + if ( (nTmp = n2 - n1 + 1 ) > nMaxRows ) nMaxRows = static_cast<SCROW>(nTmp); + + // in last pass; i = nRanges so don't use at() + if ( i < nRanges ) + pR = &(*aRangeListRef)[i]; + } + SCCOL nC = nEndCol - nStartCol + 1; + if ( nC == 1 ) + { + eGlue = ScChartGlue::Rows; + return; + } + SCROW nR = nEndRow - nStartRow + 1; + if ( nR == 1 ) + { + eGlue = ScChartGlue::Cols; + return; + } + sal_uLong nCR = static_cast<sal_uLong>(nC) * nR; + + /* + TODO: + First do it simple without bit masking. A maximum of 8MB could be allocated + this way (256 Cols x 32000 Rows). That could be reduced to 2MB by + using 2 Bits per entry, but it is faster this way. + Another optimization would be to store only used rows/columns in the array, but + would mean another iteration of the RangeList indirect access to the array. */ + + enum class CellState : sal_uInt8 { Hole, Occupied, Free, Glue }; + CellState* p; + std::unique_ptr<CellState[]> pA(new CellState[ nCR ]); + memset( pA.get(), 0, nCR * sizeof(CellState) ); + + SCCOL nCol, nCol1, nCol2; + SCROW nRow, nRow1, nRow2; + for ( size_t i = 0, nRanges = aRangeListRef->size(); i < nRanges; ++i ) + { // mark selections as used in 2D + pR = &(*aRangeListRef)[i]; + nCol1 = pR->aStart.Col() - nStartCol; + nCol2 = pR->aEnd.Col() - nStartCol; + nRow1 = pR->aStart.Row() - nStartRow; + nRow2 = pR->aEnd.Row() - nStartRow; + for ( nCol = nCol1; nCol <= nCol2; nCol++ ) + { + p = pA.get() + static_cast<sal_uLong>(nCol) * nR + nRow1; + for ( nRow = nRow1; nRow <= nRow2; nRow++, p++ ) + *p = CellState::Occupied; + } + } + bool bGlue = true; + + bool bGlueCols = false; + for ( nCol = 0; bGlue && nCol < nC; nCol++ ) + { // iterate columns and try to mark as unused + p = pA.get() + static_cast<sal_uLong>(nCol) * nR; + for ( nRow = 0; bGlue && nRow < nR; nRow++, p++ ) + { + if ( *p == CellState::Occupied ) + { // If there's one right in the middle, we can't combine. + // If it were at the edge, we could combine, if in this Column + // in every set line, one is set. + if ( nRow > 0 && nCol > 0 ) + bGlue = false; // nCol==0 can be DummyUpperLeft + else + nRow = nR; + } + else + *p = CellState::Free; + } + if ( bGlue ) + { + p = pA.get() + (((static_cast<sal_uLong>(nCol)+1) * nR) - 1); + if (*p == CellState::Free) + { // mark column as totally unused + *p = CellState::Glue; + bGlueCols = true; // one unused column at least + } + } + } + + bool bGlueRows = false; + for ( nRow = 0; bGlue && nRow < nR; nRow++ ) + { // iterate rows and try to mark as unused + p = pA.get() + nRow; + for ( nCol = 0; bGlue && nCol < nC; nCol++, p+=nR ) + { + if ( *p == CellState::Occupied ) + { + if ( nCol > 0 && nRow > 0 ) + bGlue = false; // nRow==0 can be DummyUpperLeft + else + nCol = nC; + } + else + *p = CellState::Free; + } + if ( bGlue ) + { + p = pA.get() + (((static_cast<sal_uLong>(nC)-1) * nR) + nRow); + if (*p == CellState::Free ) + { // mark row as totally unused + *p = CellState::Glue; + bGlueRows = true; // one unused row at least + } + } + } + + // If n=1: The upper left corner could be automagically pulled in for labeling + p = pA.get() + 1; + for ( sal_uLong n = 1; bGlue && n < nCR; n++, p++ ) + { // An untouched field means we could neither reach it through rows nor columns, + // thus we can't combine anything + if ( *p == CellState::Hole ) + bGlue = false; + } + if ( bGlue ) + { + if ( bGlueCols && bGlueRows ) + eGlue = ScChartGlue::Both; + else if ( bGlueRows ) + eGlue = ScChartGlue::Rows; + else + eGlue = ScChartGlue::Cols; + if ( pA[0] != CellState::Occupied ) + bDummyUpperLeft = true; + } + else + { + eGlue = ScChartGlue::NONE; + } +} + +void ScChartPositioner::CheckColRowHeaders() +{ + SCCOL nCol1, nCol2, iCol; + SCROW nRow1, nRow2, iRow; + SCTAB nTab1, nTab2; + + bool bColStrings = true; + bool bRowStrings = true; + GlueState(); + if ( aRangeListRef->size() == 1 ) + { + aRangeListRef->front().GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + if ( nCol1 > nCol2 || nRow1 > nRow2 ) + bColStrings = bRowStrings = false; + else + { + for (iCol=nCol1; iCol<=nCol2 && bColStrings; iCol++) + { + if (lcl_hasValueDataButNoDates( rDocument, iCol, nRow1, nTab1 )) + bColStrings = false; + } + for (iRow=nRow1; iRow<=nRow2 && bRowStrings; iRow++) + { + if (lcl_hasValueDataButNoDates( rDocument, nCol1, iRow, nTab1 )) + bRowStrings = false; + } + } + } + else + { + bool bVert = (eGlue == ScChartGlue::NONE || eGlue == ScChartGlue::Rows); + for ( size_t i = 0, nRanges = aRangeListRef->size(); + (i < nRanges) && (bColStrings || bRowStrings); + ++i + ) + { + const ScRange & rR = (*aRangeListRef)[i]; + rR.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + bool bTopRow = (nRow1 == nStartRow); + if ( bRowStrings && (bVert || nCol1 == nStartCol) ) + { // NONE or ROWS: RowStrings in every selection possible + // COLS or BOTH: only from first column + if ( nCol1 <= nCol2 ) + for (iRow=nRow1; iRow<=nRow2 && bRowStrings; iRow++) + { + if (lcl_hasValueDataButNoDates( rDocument, nCol1, iRow, nTab1 )) + bRowStrings = false; + } + } + if ( bColStrings && bTopRow ) + { // ColStrings only from first row + if ( nRow1 <= nRow2 ) + for (iCol=nCol1; iCol<=nCol2 && bColStrings; iCol++) + { + if (lcl_hasValueDataButNoDates( rDocument, iCol, nRow1, nTab1 )) + bColStrings = false; + } + } + } + } + bColHeaders = bColStrings; + bRowHeaders = bRowStrings; +} + +const ScChartPositionMap* ScChartPositioner::GetPositionMap() +{ + CreatePositionMap(); + return pPositionMap.get(); +} + +void ScChartPositioner::CreatePositionMap() +{ + if ( eGlue == ScChartGlue::NA && pPositionMap ) + { + pPositionMap.reset(); + } + + if ( pPositionMap ) + return ; + + SCSIZE nColAdd = bRowHeaders ? 1 : 0; + SCSIZE nRowAdd = bColHeaders ? 1 : 0; + + SCCOL nCol, nCol1, nCol2; + SCROW nRow, nRow1, nRow2; + SCTAB nTab, nTab1, nTab2; + + // real size (without hidden rows/columns) + + SCSIZE nColCount = 0; + SCSIZE nRowCount = 0; + + GlueState(); + + const bool bNoGlue = (eGlue == ScChartGlue::NONE); + ColumnMap aColMap; + SCROW nNoGlueRow = 0; + for ( size_t i = 0, nRanges = aRangeListRef->size(); i < nRanges; ++i ) + { + const ScRange & rR = (*aRangeListRef)[i]; + rR.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + for ( nTab = nTab1; nTab <= nTab2; nTab++ ) + { + // nTab in ColKey to allow to have the same col/row in another table + sal_uLong nInsCol = (static_cast<sal_uLong>(nTab) << 16) | (bNoGlue ? 0 : + static_cast<sal_uLong>(nCol1)); + for ( nCol = nCol1; nCol <= nCol2; ++nCol, ++nInsCol ) + { + RowMap* pCol = &aColMap[nInsCol]; + + // in other table a new ColKey already was created, + // the rows must be equal to be filled with Dummy + sal_uLong nInsRow = (bNoGlue ? nNoGlueRow : nRow1); + for ( nRow = nRow1; nRow <= nRow2; nRow++, nInsRow++ ) + { + if ( pCol->find( nInsRow ) == pCol->end() ) + { + pCol->emplace( nInsRow, std::make_unique<ScAddress>( nCol, nRow, nTab ) ); + } + } + } + } + // For NoGlue: associated tables will be rendered as ColGlue + nNoGlueRow += nRow2 - nRow1 + 1; + } + + // count of data + nColCount = static_cast< SCSIZE >( aColMap.size()); + if ( !aColMap.empty() ) + { + RowMap& rCol = aColMap.begin()->second; + if ( bDummyUpperLeft ) + rCol[ 0 ] = nullptr; // Dummy for labeling + nRowCount = static_cast< SCSIZE >( rCol.size()); + } + else + nRowCount = 0; + if ( nColCount > 0 ) + nColCount -= nColAdd; + if ( nRowCount > 0 ) + nRowCount -= nRowAdd; + + if ( nColCount==0 || nRowCount==0 ) + { // create an entry without data + RowMap& rCol = aColMap[0]; + nColCount = 1; + rCol[ 0 ] = nullptr; + nRowCount = 1; + nColAdd = 0; + nRowAdd = 0; + } + else + { + if ( bNoGlue ) + { // fill gaps with Dummies, first column is master + RowMap& rFirstCol = aColMap.begin()->second; + + for ( const auto& it1 : rFirstCol ) + { + sal_uLong nKey = it1.first; + for (ColumnMap::iterator it2 = ++aColMap.begin(); it2 != aColMap.end(); ++it2 ) + it2->second.emplace( nKey, nullptr ); // no data + } + } + } + + pPositionMap.reset( new ScChartPositionMap( static_cast<SCCOL>(nColCount), static_cast<SCROW>(nRowCount), + static_cast<SCCOL>(nColAdd), static_cast<SCROW>(nRowAdd), aColMap ) ); +} + +void ScChartPositioner::InvalidateGlue() +{ + eGlue = ScChartGlue::NA; + pPositionMap.reset(); +} + +ScChartPositionMap::ScChartPositionMap( SCCOL nChartCols, SCROW nChartRows, + SCCOL nColAdd, SCROW nRowAdd, ColumnMap& rCols ) : + ppData( new std::unique_ptr<ScAddress> [ nChartCols * nChartRows ] ), + ppColHeader( new std::unique_ptr<ScAddress> [ nChartCols ] ), + ppRowHeader( new std::unique_ptr<ScAddress> [ nChartRows ] ), + nCount( static_cast<sal_uLong>(nChartCols) * nChartRows ), + nColCount( nChartCols ), + nRowCount( nChartRows ) +{ + OSL_ENSURE( nColCount && nRowCount, "ScChartPositionMap without dimension" ); + + ColumnMap::iterator pColIter = rCols.begin(); + RowMap& rCol1 = pColIter->second; + + // row header + auto pPos1Iter = rCol1.begin(); + if ( nRowAdd ) + ++pPos1Iter; + if ( nColAdd ) + { // independent + SCROW nRow = 0; + for ( ; nRow < nRowCount && pPos1Iter != rCol1.end(); nRow++ ) + { + ppRowHeader[ nRow ] = std::move(pPos1Iter->second); + ++pPos1Iter; + } + } + else + { // copy + SCROW nRow = 0; + for ( ; nRow < nRowCount && pPos1Iter != rCol1.end(); nRow++ ) + { + if (pPos1Iter->second) + ppRowHeader[ nRow ].reset(new ScAddress( *pPos1Iter->second )); + ++pPos1Iter; + } + } + if ( nColAdd ) + { + ++pColIter; + } + + // data column by column and column-header + sal_uLong nIndex = 0; + for ( SCCOL nCol = 0; nCol < nColCount; nCol++ ) + { + if ( pColIter != rCols.end() ) + { + RowMap& rCol2 = pColIter->second; + RowMap::iterator pPosIter = rCol2.begin(); + if ( pPosIter != rCol2.end() ) + { + if ( nRowAdd ) + { + ppColHeader[ nCol ] = std::move(pPosIter->second); // independent + ++pPosIter; + } + else if ( pPosIter->second ) + ppColHeader[ nCol ].reset( new ScAddress( *pPosIter->second ) ); + } + + SCROW nRow = 0; + for ( ; nRow < nRowCount && pPosIter != rCol2.end(); nRow++, nIndex++ ) + { + ppData[ nIndex ] = std::move(pPosIter->second); + ++pPosIter; + } + + ++pColIter; + } + } +} + +ScChartPositionMap::~ScChartPositionMap() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/chgtrack.cxx b/sc/source/core/tool/chgtrack.cxx new file mode 100644 index 0000000000..53fe660f10 --- /dev/null +++ b/sc/source/core/tool/chgtrack.cxx @@ -0,0 +1,4682 @@ +/* -*- 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 <chgtrack.hxx> +#include <compiler.hxx> +#include <formulacell.hxx> +#include <document.hxx> +#include <docsh.hxx> +#include <dociter.hxx> +#include <global.hxx> +#include <scmod.hxx> +#include <inputopt.hxx> +#include <patattr.hxx> +#include <hints.hxx> +#include <markdata.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <editutil.hxx> +#include <tokenarray.hxx> +#include <refupdatecontext.hxx> +#include <refupdat.hxx> + +#include <osl/diagnose.h> +#include <svl/numformat.hxx> +#include <sfx2/objsh.hxx> +#include <unotools/useroptions.hxx> +#include <unotools/datetime.hxx> +#include <tools/json_writer.hxx> +#include <algorithm> +#include <memory> +#include <strings.hrc> +#include <utility> + +ScChangeAction::ScChangeAction( ScChangeActionType eTypeP, const ScRange& rRange ) + : + aBigRange( rRange ), + aDateTime( DateTime::SYSTEM ), + pNext( nullptr ), + pPrev( nullptr ), + pLinkAny( nullptr ), + pLinkDeletedIn( nullptr ), + pLinkDeleted( nullptr ), + pLinkDependent( nullptr ), + nAction( 0 ), + nRejectAction( 0 ), + eType( eTypeP ), + eState( SC_CAS_VIRGIN ) +{ + aDateTime.ConvertToUTC(); +} + +ScChangeAction::ScChangeAction( + ScChangeActionType eTypeP, ScBigRange aRange, + const sal_uLong nTempAction, const sal_uLong nTempRejectAction, + const ScChangeActionState eTempState, const DateTime& aTempDateTime, + OUString aTempUser, OUString aTempComment) : + aBigRange(std::move( aRange )), + aDateTime( aTempDateTime ), + aUser(std::move( aTempUser )), + aComment(std::move( aTempComment )), + pNext( nullptr ), + pPrev( nullptr ), + pLinkAny( nullptr ), + pLinkDeletedIn( nullptr ), + pLinkDeleted( nullptr ), + pLinkDependent( nullptr ), + nAction( nTempAction ), + nRejectAction( nTempRejectAction ), + eType( eTypeP ), + eState( eTempState ) +{ +} + +ScChangeAction::ScChangeAction( ScChangeActionType eTypeP, ScBigRange aRange, + const sal_uLong nTempAction) + : + aBigRange(std::move( aRange )), + aDateTime( DateTime::SYSTEM ), + pNext( nullptr ), + pPrev( nullptr ), + pLinkAny( nullptr ), + pLinkDeletedIn( nullptr ), + pLinkDeleted( nullptr ), + pLinkDependent( nullptr ), + nAction( nTempAction ), + nRejectAction( 0 ), + eType( eTypeP ), + eState( SC_CAS_VIRGIN ) +{ + aDateTime.ConvertToUTC(); +} + +ScChangeAction::~ScChangeAction() +{ + RemoveAllLinks(); +} + +bool ScChangeAction::IsInsertType() const +{ + return eType == SC_CAT_INSERT_COLS || eType == SC_CAT_INSERT_ROWS || eType == SC_CAT_INSERT_TABS; +} + +bool ScChangeAction::IsDeleteType() const +{ + return eType == SC_CAT_DELETE_COLS || eType == SC_CAT_DELETE_ROWS || eType == SC_CAT_DELETE_TABS; +} + +bool ScChangeAction::IsVirgin() const +{ + return eState == SC_CAS_VIRGIN; +} + +bool ScChangeAction::IsAccepted() const +{ + return eState == SC_CAS_ACCEPTED; +} + +bool ScChangeAction::IsRejected() const +{ + return eState == SC_CAS_REJECTED; +} + +bool ScChangeAction::IsRejecting() const +{ + return nRejectAction != 0; +} + +bool ScChangeAction::IsVisible() const +{ + // sequence order of execution is significant! + if ( IsRejected() || GetType() == SC_CAT_DELETE_TABS || IsDeletedIn() ) + return false; + if ( GetType() == SC_CAT_CONTENT ) + return static_cast<const ScChangeActionContent*>(this)->IsTopContent(); + return true; +} + +bool ScChangeAction::IsTouchable() const +{ + // sequence order of execution is significant! + if ( IsRejected() || GetType() == SC_CAT_REJECT || IsDeletedIn() ) + return false; + // content may reject and be touchable if on top + if ( GetType() == SC_CAT_CONTENT ) + return static_cast<const ScChangeActionContent*>(this)->IsTopContent(); + if ( IsRejecting() ) + return false; + return true; +} + +bool ScChangeAction::IsClickable() const +{ + // sequence order of execution is significant! + if ( !IsVirgin() ) + return false; + if ( IsDeletedIn() ) + return false; + if ( GetType() == SC_CAT_CONTENT ) + { + ScChangeActionContentCellType eCCT = + ScChangeActionContent::GetContentCellType( + static_cast<const ScChangeActionContent*>(this)->GetNewCell() ); + if ( eCCT == SC_CACCT_MATREF ) + return false; + if ( eCCT == SC_CACCT_MATORG ) + { // no Accept-Select if one of the references is in a deleted col/row + const ScChangeActionLinkEntry* pL = + static_cast<const ScChangeActionContent*>(this)->GetFirstDependentEntry(); + while ( pL ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pL->GetAction()); + if ( p && p->IsDeletedIn() ) + return false; + pL = pL->GetNext(); + } + } + return true; // for Select() a content doesn't have to be touchable + } + return IsTouchable(); // Accept()/Reject() only on touchables +} + +bool ScChangeAction::IsRejectable() const +{ + // sequence order of execution is significant! + if ( !IsClickable() ) + return false; + if ( GetType() == SC_CAT_CONTENT ) + { + if ( static_cast<const ScChangeActionContent*>(this)->IsOldMatrixReference() ) + return false; + ScChangeActionContent* pNextContent = + static_cast<const ScChangeActionContent*>(this)->GetNextContent(); + if ( pNextContent == nullptr ) + return true; // *this is TopContent + return pNextContent->IsRejected(); // *this is next rejectable + } + return IsTouchable(); +} + +bool ScChangeAction::IsInternalRejectable() const +{ + // sequence order of execution is significant! + if ( !IsVirgin() ) + return false; + if ( IsDeletedIn() ) + return false; + if ( GetType() == SC_CAT_CONTENT ) + { + ScChangeActionContent* pNextContent = + static_cast<const ScChangeActionContent*>(this)->GetNextContent(); + if ( pNextContent == nullptr ) + return true; // *this is TopContent + return pNextContent->IsRejected(); // *this is next rejectable + } + return IsTouchable(); +} + +bool ScChangeAction::IsDialogRoot() const +{ + return IsInternalRejectable(); // only rejectables in root +} + +bool ScChangeAction::IsDialogParent() const +{ + // sequence order of execution is significant! + if ( GetType() == SC_CAT_CONTENT ) + { + if ( !IsDialogRoot() ) + return false; + if ( static_cast<const ScChangeActionContent*>(this)->IsMatrixOrigin() && HasDependent() ) + return true; + ScChangeActionContent* pPrevContent = + static_cast<const ScChangeActionContent*>(this)->GetPrevContent(); + return pPrevContent && pPrevContent->IsVirgin(); + } + if ( HasDependent() ) + return IsDeleteType() || !IsDeletedIn(); + if ( HasDeleted() ) + { + if ( IsDeleteType() ) + { + if ( IsDialogRoot() ) + return true; + ScChangeActionLinkEntry* pL = pLinkDeleted; + while ( pL ) + { + ScChangeAction* p = pL->GetAction(); + if ( p && p->GetType() != eType ) + return true; + pL = pL->GetNext(); + } + } + else + return true; + } + return false; +} + +bool ScChangeAction::IsMasterDelete() const +{ + if ( !IsDeleteType() ) + return false; + const ScChangeActionDel* pDel = static_cast<const ScChangeActionDel*>(this); + return pDel->IsMultiDelete() && (pDel->IsTopDelete() || pDel->IsRejectable()); +} + +void ScChangeAction::RemoveAllLinks() +{ + while (pLinkAny) + { + // coverity[use_after_free] - Moves up by itself + delete pLinkAny; + } + + RemoveAllDeletedIn(); + + while (pLinkDeleted) + { + // coverity[use_after_free] - Moves up by itself + delete pLinkDeleted; + } + + RemoveAllDependent(); +} + +bool ScChangeAction::RemoveDeletedIn( const ScChangeAction* p ) +{ + bool bRemoved = false; + ScChangeActionLinkEntry* pL = GetDeletedIn(); + while ( pL ) + { + ScChangeActionLinkEntry* pNextLink = pL->GetNext(); + if ( pL->GetAction() == p ) + { + delete pL; + bRemoved = true; + } + pL = pNextLink; + } + return bRemoved; +} + +bool ScChangeAction::IsDeletedIn() const +{ + return GetDeletedIn() != nullptr; +} + +bool ScChangeAction::IsDeletedIn( const ScChangeAction* p ) const +{ + ScChangeActionLinkEntry* pL = GetDeletedIn(); + while ( pL ) + { + if ( pL->GetAction() == p ) + return true; + pL = pL->GetNext(); + } + return false; +} + +void ScChangeAction::RemoveAllDeletedIn() +{ + //TODO: Not from TopContent, but really this one + while (pLinkDeletedIn) + { + // coverity[use_after_free] - Moves up by itself + delete pLinkDeletedIn; + } +} + +bool ScChangeAction::IsDeletedInDelType( ScChangeActionType eDelType ) const +{ + ScChangeActionLinkEntry* pL = GetDeletedIn(); + if ( pL ) + { + // InsertType for MergePrepare/MergeOwn + ScChangeActionType eInsType; + switch ( eDelType ) + { + case SC_CAT_DELETE_COLS : + eInsType = SC_CAT_INSERT_COLS; + break; + case SC_CAT_DELETE_ROWS : + eInsType = SC_CAT_INSERT_ROWS; + break; + case SC_CAT_DELETE_TABS : + eInsType = SC_CAT_INSERT_TABS; + break; + default: + eInsType = SC_CAT_NONE; + } + while ( pL ) + { + ScChangeAction* p = pL->GetAction(); + if ( p != nullptr && (p->GetType() == eDelType || p->GetType() == eInsType) ) + return true; + pL = pL->GetNext(); + } + } + return false; +} + +bool ScChangeAction::HasDependent() const +{ + return pLinkDependent != nullptr; +} + +bool ScChangeAction::HasDeleted() const +{ + return pLinkDeleted != nullptr; +} + +void ScChangeAction::SetDeletedIn( ScChangeAction* p ) +{ + ScChangeActionLinkEntry* pLink1 = new ScChangeActionLinkEntry( GetDeletedInAddress(), p ); + ScChangeActionLinkEntry* pLink2; + if ( GetType() == SC_CAT_CONTENT ) + pLink2 = p->AddDeleted( static_cast<ScChangeActionContent*>(this)->GetTopContent() ); + else + pLink2 = p->AddDeleted( this ); + pLink1->SetLink( pLink2 ); +} + +void ScChangeAction::RemoveAllDependent() +{ + while (pLinkDependent) + { + // coverity[use_after_free] - Moves up by itself + delete pLinkDependent; + } +} + +DateTime ScChangeAction::GetDateTime() const +{ + DateTime aDT( aDateTime ); + aDT.ConvertToLocalTime(); + return aDT; +} + +void ScChangeAction::UpdateReference( const ScChangeTrack* /* pTrack */, + UpdateRefMode eMode, const ScBigRange& rRange, + sal_Int32 nDx, sal_Int32 nDy, sal_Int32 nDz ) +{ + ScRefUpdate::Update( eMode, rRange, nDx, nDy, nDz, GetBigRange() ); +} + +OUString ScChangeAction::GetDescription( + ScDocument& /* rDoc */, bool /* bSplitRange */, bool bWarning ) const +{ + if (!IsRejecting() || !bWarning) + return OUString(); + + // Add comment if rejection may have resulted in references + // not properly restored in formulas. See specification at + // http://specs.openoffice.org/calc/ease-of-use/redlining_comment.sxw + + if (GetType() == SC_CAT_MOVE) + { + return ScResId(STR_CHANGED_MOVE_REJECTION_WARNING) + " "; + } + + if (IsInsertType()) + { + return ScResId(STR_CHANGED_DELETE_REJECTION_WARNING) + " "; + } + + const ScChangeTrack* pCT = GetChangeTrack(); + if (!pCT) + return OUString(); + + ScChangeAction* pReject = pCT->GetActionOrGenerated(GetRejectAction()); + + if (!pReject) + return OUString(); + + if (pReject->GetType() == SC_CAT_MOVE) + { + return ScResId(STR_CHANGED_MOVE_REJECTION_WARNING) + " "; + } + + if (pReject->IsDeleteType()) + { + return ScResId(STR_CHANGED_DELETE_REJECTION_WARNING) + " "; + } + + if (!pReject->HasDependent()) + return OUString(); + + ScChangeActionMap aMap; + pCT->GetDependents( pReject, aMap, false, true ); + ScChangeActionMap::iterator itChangeAction = std::find_if(aMap.begin(), aMap.end(), + [&pReject](const ScChangeActionMap::value_type& rEntry) { + return rEntry.second->GetType() == SC_CAT_MOVE || pReject->IsDeleteType(); }); + if (itChangeAction == aMap.end()) + return OUString(); + + if( itChangeAction->second->GetType() == SC_CAT_MOVE) + return ScResId(STR_CHANGED_MOVE_REJECTION_WARNING) + " "; + else + return ScResId(STR_CHANGED_DELETE_REJECTION_WARNING) + " "; +} + +OUString ScChangeAction::GetRefString( + const ScBigRange& rRange, const ScDocument& rDoc, bool bFlag3D ) const +{ + OUStringBuffer aBuf; + ScRefFlags nFlags = ( rRange.IsValid( rDoc ) ? ScRefFlags::VALID : ScRefFlags::ZERO ); + if ( nFlags == ScRefFlags::ZERO ) + aBuf.append(ScCompiler::GetNativeSymbol(ocErrRef)); + else + { + ScRange aTmpRange( rRange.MakeRange( rDoc ) ); + switch ( GetType() ) + { + case SC_CAT_INSERT_COLS : + case SC_CAT_DELETE_COLS : + if ( bFlag3D ) + { + OUString aTmp; + rDoc.GetName( aTmpRange.aStart.Tab(), aTmp ); + aBuf.append(aTmp + "."); + } + aBuf.append(ScColToAlpha(aTmpRange.aStart.Col()) + + ":" + ScColToAlpha(aTmpRange.aEnd.Col())); + break; + case SC_CAT_INSERT_ROWS : + case SC_CAT_DELETE_ROWS : + if ( bFlag3D ) + { + OUString aTmp; + rDoc.GetName( aTmpRange.aStart.Tab(), aTmp ); + aBuf.append(aTmp + "."); + } + aBuf.append(OUString::number(static_cast<sal_Int64>(aTmpRange.aStart.Row()+1)) + + ":" + OUString::number(static_cast<sal_Int64>(aTmpRange.aEnd.Row()+1))); + break; + default: + { + if ( bFlag3D || GetType() == SC_CAT_INSERT_TABS ) + nFlags |= ScRefFlags::TAB_3D; + + aBuf.append(aTmpRange.Format(rDoc, nFlags, rDoc.GetAddressConvention())); + } + } + if ( (bFlag3D && IsDeleteType()) || IsDeletedIn() ) + { + aBuf.insert(0, '('); + aBuf.append(')'); + } + } + return aBuf.makeStringAndClear(); +} + +void ScChangeAction::SetUser( const OUString& r ) +{ + aUser = r; +} + +void ScChangeAction::SetComment( const OUString& rStr ) +{ + aComment = rStr; +} + +OUString ScChangeAction::GetRefString( ScDocument& rDoc, bool bFlag3D ) const +{ + return GetRefString( GetBigRange(), rDoc, bFlag3D ); +} + +void ScChangeAction::Accept() +{ + if ( IsVirgin() ) + { + SetState( SC_CAS_ACCEPTED ); + DeleteCellEntries(); + } +} + +void ScChangeAction::SetRejected() +{ + if ( IsVirgin() ) + { + SetState( SC_CAS_REJECTED ); + RemoveAllLinks(); + DeleteCellEntries(); + } +} + +void ScChangeAction::RejectRestoreContents( ScChangeTrack* pTrack, + SCCOL nDx, SCROW nDy ) +{ + // Construct list of Contents + std::vector<ScChangeActionContent*> aContentsList; + for ( ScChangeActionLinkEntry* pL = pLinkDeleted; pL; pL = pL->GetNext() ) + { + ScChangeAction* p = pL->GetAction(); + if ( p && p->GetType() == SC_CAT_CONTENT ) + { + aContentsList.push_back(static_cast<ScChangeActionContent*>(p) ); + } + } + SetState( SC_CAS_REJECTED ); // Before UpdateReference for Move + pTrack->UpdateReference( this, true ); // Free LinkDeleted + OSL_ENSURE( !pLinkDeleted, "ScChangeAction::RejectRestoreContents: pLinkDeleted != NULL" ); + + // Work through list of Contents and delete + ScDocument& rDoc = pTrack->GetDocument(); + for (ScChangeActionContent* pContent : aContentsList) + { + if ( !pContent->IsDeletedIn() && + pContent->GetBigRange().aStart.IsValid( rDoc ) ) + pContent->PutNewValueToDoc( &rDoc, nDx, nDy ); + } + DeleteCellEntries(); // Remove generated ones +} + +void ScChangeAction::SetDeletedInThis( sal_uLong nActionNumber, + const ScChangeTrack* pTrack ) +{ + if ( nActionNumber ) + { + ScChangeAction* pAct = pTrack->GetActionOrGenerated( nActionNumber ); + OSL_ENSURE( pAct, "ScChangeAction::SetDeletedInThis: missing Action" ); + if ( pAct ) + pAct->SetDeletedIn( this ); + } +} + +void ScChangeAction::AddDependent( sal_uLong nActionNumber, + const ScChangeTrack* pTrack ) +{ + if ( nActionNumber ) + { + ScChangeAction* pAct = pTrack->GetActionOrGenerated( nActionNumber ); + OSL_ENSURE( pAct, "ScChangeAction::AddDependent: missing Action" ); + if ( pAct ) + { + ScChangeActionLinkEntry* pLink = AddDependent( pAct ); + pAct->AddLink( this, pLink ); + } + } +} + +// ScChangeActionIns +ScChangeActionIns::ScChangeActionIns( const ScDocument* pDoc, const ScRange& rRange, bool bEndOfList ) : + ScChangeAction(SC_CAT_NONE, rRange), + mbEndOfList(bEndOfList) +{ + if ( rRange.aStart.Col() == 0 && rRange.aEnd.Col() == pDoc->MaxCol() ) + { + aBigRange.aStart.SetCol( ScBigRange::nRangeMin ); + aBigRange.aEnd.SetCol( ScBigRange::nRangeMax ); + if ( rRange.aStart.Row() == 0 && rRange.aEnd.Row() == pDoc->MaxRow() ) + { + SetType( SC_CAT_INSERT_TABS ); + aBigRange.aStart.SetRow( ScBigRange::nRangeMin ); + aBigRange.aEnd.SetRow( ScBigRange::nRangeMax ); + } + else + SetType( SC_CAT_INSERT_ROWS ); + } + else if ( rRange.aStart.Row() == 0 && rRange.aEnd.Row() == pDoc->MaxRow() ) + { + SetType( SC_CAT_INSERT_COLS ); + aBigRange.aStart.SetRow( ScBigRange::nRangeMin ); + aBigRange.aEnd.SetRow( ScBigRange::nRangeMax ); + } + else + { + OSL_FAIL( "ScChangeActionIns: Block not supported!" ); + } +} + +ScChangeActionIns::ScChangeActionIns( + const sal_uLong nActionNumber, const ScChangeActionState eStateP, + const sal_uLong nRejectingNumber, const ScBigRange& aBigRangeP, + const OUString& aUserP, const DateTime& aDateTimeP, + const OUString& sComment, const ScChangeActionType eTypeP, + bool bEndOfList ) : + ScChangeAction(eTypeP, aBigRangeP, nActionNumber, nRejectingNumber, eStateP, aDateTimeP, aUserP, sComment), + mbEndOfList(bEndOfList) +{ +} + +ScChangeActionIns::~ScChangeActionIns() +{ +} + +OUString ScChangeActionIns::GetDescription( ScDocument& rDoc, bool bSplitRange, bool bWarning ) const +{ + OUString str = ScChangeAction::GetDescription( rDoc, bSplitRange, bWarning ); + + TranslateId pWhatId; + switch ( GetType() ) + { + case SC_CAT_INSERT_COLS : + pWhatId = STR_COLUMN; + break; + case SC_CAT_INSERT_ROWS : + pWhatId = STR_ROW; + break; + default: + pWhatId = STR_AREA; + } + + OUString aRsc = ScResId(STR_CHANGED_INSERT); + sal_Int32 nPos = aRsc.indexOf("#1"); + if (nPos < 0) + return str; + + // Construct a range string to replace '#1' first. + OUString aRangeStr = ScResId(pWhatId) + + " " + + GetRefString(GetBigRange(), rDoc); + + aRsc = aRsc.replaceAt(nPos, 2, aRangeStr); // replace '#1' with the range string. + + return str + aRsc; +} + +bool ScChangeActionIns::IsEndOfList() const +{ + return mbEndOfList; +} + +bool ScChangeActionIns::Reject( ScDocument& rDoc ) +{ + if ( !aBigRange.IsValid( rDoc ) ) + return false; + + ScRange aRange( aBigRange.MakeRange( rDoc ) ); + if ( !rDoc.IsBlockEditable( aRange.aStart.Tab(), aRange.aStart.Col(), + aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row() ) ) + return false; + + switch ( GetType() ) + { + case SC_CAT_INSERT_COLS : + rDoc.DeleteCol( aRange ); + break; + case SC_CAT_INSERT_ROWS : + rDoc.DeleteRow( aRange ); + break; + case SC_CAT_INSERT_TABS : + rDoc.DeleteTab( aRange.aStart.Tab() ); + break; + default: + { + // added to avoid warnings + } + } + SetState( SC_CAS_REJECTED ); + RemoveAllLinks(); + return true; +} + +// ScChangeActionDel +ScChangeActionDel::ScChangeActionDel( const ScDocument* pDoc, const ScRange& rRange, + SCCOL nDxP, SCROW nDyP, ScChangeTrack* pTrackP ) + : + ScChangeAction( SC_CAT_NONE, rRange ), + pTrack( pTrackP ), + pCutOff( nullptr ), + nCutOff( 0 ), + pLinkMove( nullptr ), + nDx( nDxP ), + nDy( nDyP ) +{ + if ( rRange.aStart.Col() == 0 && rRange.aEnd.Col() == pDoc->MaxCol() ) + { + aBigRange.aStart.SetCol( ScBigRange::nRangeMin ); + aBigRange.aEnd.SetCol( ScBigRange::nRangeMax ); + if ( rRange.aStart.Row() == 0 && rRange.aEnd.Row() == pDoc->MaxRow() ) + { + SetType( SC_CAT_DELETE_TABS ); + aBigRange.aStart.SetRow( ScBigRange::nRangeMin ); + aBigRange.aEnd.SetRow( ScBigRange::nRangeMax ); + } + else + SetType( SC_CAT_DELETE_ROWS ); + } + else if ( rRange.aStart.Row() == 0 && rRange.aEnd.Row() == pDoc->MaxRow() ) + { + SetType( SC_CAT_DELETE_COLS ); + aBigRange.aStart.SetRow( ScBigRange::nRangeMin ); + aBigRange.aEnd.SetRow( ScBigRange::nRangeMax ); + } + else + { + OSL_FAIL( "ScChangeActionDel: Block not supported!" ); + } +} + +ScChangeActionDel::ScChangeActionDel( + const sal_uLong nActionNumber, const ScChangeActionState eStateP, + const sal_uLong nRejectingNumber, const ScBigRange& aBigRangeP, + const OUString& aUserP, const DateTime& aDateTimeP, const OUString &sComment, + const ScChangeActionType eTypeP, const SCCOLROW nD, ScChangeTrack* pTrackP) : // which of nDx and nDy is set depends on the type + ScChangeAction(eTypeP, aBigRangeP, nActionNumber, nRejectingNumber, eStateP, aDateTimeP, aUserP, sComment), + pTrack( pTrackP ), + pCutOff( nullptr ), + nCutOff( 0 ), + pLinkMove( nullptr ), + nDx( 0 ), + nDy( 0 ) +{ + if (eType == SC_CAT_DELETE_COLS) + nDx = static_cast<SCCOL>(nD); + else if (eType == SC_CAT_DELETE_ROWS) + nDy = static_cast<SCROW>(nD); +} + +ScChangeActionDel::~ScChangeActionDel() +{ + DeleteCellEntries(); + while (pLinkMove) + { + // coverity[use_after_free] - Moves up by itself + delete pLinkMove; + } +} + +void ScChangeActionDel::AddContent( ScChangeActionContent* pContent ) +{ + mvCells.push_back(pContent); +} + +void ScChangeActionDel::DeleteCellEntries() +{ + pTrack->DeleteCellEntries( mvCells, this ); +} + +bool ScChangeActionDel::IsBaseDelete() const +{ + return !GetDx() && !GetDy(); +} + +bool ScChangeActionDel::IsTopDelete() const +{ + const ScChangeAction* p = GetNext(); + if ( !p || p->GetType() != GetType() ) + return true; + return static_cast<const ScChangeActionDel*>(p)->IsBaseDelete(); +} + +bool ScChangeActionDel::IsMultiDelete() const +{ + if ( GetDx() || GetDy() ) + return true; + const ScChangeAction* p = GetNext(); + if ( !p || p->GetType() != GetType() ) + return false; + const ScChangeActionDel* pDel = static_cast<const ScChangeActionDel*>(p); + return (pDel->GetDx() > GetDx() || pDel->GetDy() > GetDy()) && + pDel->GetBigRange() == aBigRange; +} + +bool ScChangeActionDel::IsTabDeleteCol() const +{ + if ( GetType() != SC_CAT_DELETE_COLS ) + return false; + const ScChangeAction* p = this; + while ( p && p->GetType() == SC_CAT_DELETE_COLS && + !static_cast<const ScChangeActionDel*>(p)->IsTopDelete() ) + p = p->GetNext(); + return p && p->GetType() == SC_CAT_DELETE_TABS; +} + +ScChangeActionDelMoveEntry* ScChangeActionDel::AddCutOffMove( + ScChangeActionMove* pMove, short nFrom, short nTo ) +{ + return new ScChangeActionDelMoveEntry(&pLinkMove, pMove, nFrom, nTo); +} + +void ScChangeActionDel::UpdateReference( const ScChangeTrack* /* pTrack */, + UpdateRefMode eMode, const ScBigRange& rRange, + sal_Int32 nDxP, sal_Int32 nDyP, sal_Int32 nDz ) +{ + ScRefUpdate::Update( eMode, rRange, nDxP, nDyP, nDz, GetBigRange() ); + + if ( !IsDeletedIn() ) + return ; + + // Correct in the ones who slipped through + for ( ScChangeActionLinkEntry* pL = pLinkDeleted; pL; pL = pL->GetNext() ) + { + ScChangeAction* p = pL->GetAction(); + if ( p && p->GetType() == SC_CAT_CONTENT && + !GetBigRange().Contains( p->GetBigRange() ) ) + { + switch ( GetType() ) + { + case SC_CAT_DELETE_COLS : + p->GetBigRange().aStart.SetCol( GetBigRange().aStart.Col() ); + p->GetBigRange().aEnd.SetCol( GetBigRange().aStart.Col() ); + break; + case SC_CAT_DELETE_ROWS : + p->GetBigRange().aStart.SetRow( GetBigRange().aStart.Row() ); + p->GetBigRange().aEnd.SetRow( GetBigRange().aStart.Row() ); + break; + case SC_CAT_DELETE_TABS : + p->GetBigRange().aStart.SetTab( GetBigRange().aStart.Tab() ); + p->GetBigRange().aEnd.SetTab( GetBigRange().aStart.Tab() ); + break; + default: + { + // added to avoid warnings + } + } + } + } +} + +ScBigRange ScChangeActionDel::GetOverAllRange() const +{ + ScBigRange aTmpRange( GetBigRange() ); + aTmpRange.aEnd.SetCol( aTmpRange.aEnd.Col() + GetDx() ); + aTmpRange.aEnd.SetRow( aTmpRange.aEnd.Row() + GetDy() ); + return aTmpRange; +} + +OUString ScChangeActionDel::GetDescription( ScDocument& rDoc, bool bSplitRange, bool bWarning ) const +{ + OUString str = ScChangeAction::GetDescription( rDoc, bSplitRange, bWarning ); + + TranslateId pWhatId; + switch ( GetType() ) + { + case SC_CAT_DELETE_COLS : + pWhatId = STR_COLUMN; + break; + case SC_CAT_DELETE_ROWS : + pWhatId = STR_ROW; + break; + default: + pWhatId = STR_AREA; + } + + ScBigRange aTmpRange( GetBigRange() ); + if ( !IsRejected() ) + { + if ( bSplitRange ) + { + aTmpRange.aStart.SetCol( aTmpRange.aStart.Col() + GetDx() ); + aTmpRange.aStart.SetRow( aTmpRange.aStart.Row() + GetDy() ); + } + aTmpRange.aEnd.SetCol( aTmpRange.aEnd.Col() + GetDx() ); + aTmpRange.aEnd.SetRow( aTmpRange.aEnd.Row() + GetDy() ); + } + + OUString aRsc = ScResId(STR_CHANGED_DELETE); + sal_Int32 nPos = aRsc.indexOf("#1"); + if (nPos < 0) + return str; + + // Build a string to replace with. + OUString aRangeStr = ScResId(pWhatId) + " " + + GetRefString(aTmpRange, rDoc); + aRsc = aRsc.replaceAt(nPos, 2, aRangeStr); // replace '#1' with the string. + + return str + aRsc; // append to the original. +} + +bool ScChangeActionDel::Reject( ScDocument& rDoc ) +{ + if ( !aBigRange.IsValid( rDoc ) && GetType() != SC_CAT_DELETE_TABS ) + return false; + + if ( IsTopDelete() ) + { // Restore whole section in one go + bool bOk = true; + ScBigRange aTmpRange( GetOverAllRange() ); + if ( !aTmpRange.IsValid( rDoc ) ) + { + if ( GetType() == SC_CAT_DELETE_TABS ) + { // Do we attach a Tab? + if ( aTmpRange.aStart.Tab() > rDoc.GetMaxTableNumber() ) + bOk = false; + } + else + bOk = false; + } + if ( bOk ) + { + ScRange aRange( aTmpRange.MakeRange( rDoc ) ); + // InDelete... for formula UpdateReference in Document + pTrack->SetInDeleteRange( aRange ); + pTrack->SetInDeleteTop( true ); + pTrack->SetInDeleteUndo( true ); + pTrack->SetInDelete( true ); + switch ( GetType() ) + { + case SC_CAT_DELETE_COLS : + if ( aRange.aStart.Col() != 0 || aRange.aEnd.Col() != rDoc.MaxCol() ) + { // Only if not TabDelete + bOk = rDoc.CanInsertCol( aRange ) && rDoc.InsertCol( aRange ); + } + break; + case SC_CAT_DELETE_ROWS : + bOk = rDoc.CanInsertRow( aRange ) && rDoc.InsertRow( aRange ); + break; + case SC_CAT_DELETE_TABS : + { + //TODO: Remember table names? + OUString aName; + rDoc.CreateValidTabName( aName ); + bOk = rDoc.ValidNewTabName( aName ) && rDoc.InsertTab( aRange.aStart.Tab(), aName ); + } + break; + default: + { + // added to avoid warnings + } + } + pTrack->SetInDelete( false ); + pTrack->SetInDeleteUndo( false ); + } + if ( !bOk ) + { + pTrack->SetInDeleteTop( false ); + return false; + } + // Keep InDeleteTop for UpdateReference Undo + } + + // Sets rejected and calls UpdateReference-Undo and DeleteCellEntries + RejectRestoreContents( pTrack, GetDx(), GetDy() ); + + pTrack->SetInDeleteTop( false ); + RemoveAllLinks(); + return true; +} + +void ScChangeActionDel::UndoCutOffMoves() +{ // Restore cut off Moves; delete Entries/Links + while ( pLinkMove ) + { + // coverity[deref_arg] - the call on delete pLinkMove at the block end Moves a new entry into pLinkMode by itself + ScChangeActionMove* pMove = pLinkMove->GetMove(); + short nFrom = pLinkMove->GetCutOffFrom(); + short nTo = pLinkMove->GetCutOffTo(); + switch ( GetType() ) + { + case SC_CAT_DELETE_COLS : + if ( nFrom > 0 ) + pMove->GetFromRange().aStart.IncCol( -nFrom ); + else if ( nFrom < 0 ) + pMove->GetFromRange().aEnd.IncCol( -nFrom ); + if ( nTo > 0 ) + pMove->GetBigRange().aStart.IncCol( -nTo ); + else if ( nTo < 0 ) + pMove->GetBigRange().aEnd.IncCol( -nTo ); + break; + case SC_CAT_DELETE_ROWS : + if ( nFrom > 0 ) + pMove->GetFromRange().aStart.IncRow( -nFrom ); + else if ( nFrom < 0 ) + pMove->GetFromRange().aEnd.IncRow( -nFrom ); + if ( nTo > 0 ) + pMove->GetBigRange().aStart.IncRow( -nTo ); + else if ( nTo < 0 ) + pMove->GetBigRange().aEnd.IncRow( -nTo ); + break; + case SC_CAT_DELETE_TABS : + if ( nFrom > 0 ) + pMove->GetFromRange().aStart.IncTab( -nFrom ); + else if ( nFrom < 0 ) + pMove->GetFromRange().aEnd.IncTab( -nFrom ); + if ( nTo > 0 ) + pMove->GetBigRange().aStart.IncTab( -nTo ); + else if ( nTo < 0 ) + pMove->GetBigRange().aEnd.IncTab( -nTo ); + break; + default: + { + // added to avoid warnings + } + } + delete pLinkMove; // Moves up by itself + } +} + +void ScChangeActionDel::UndoCutOffInsert() +{ //Restore cut off Insert + if ( !pCutOff ) + return; + + switch ( pCutOff->GetType() ) + { + case SC_CAT_INSERT_COLS : + if ( nCutOff < 0 ) + pCutOff->GetBigRange().aEnd.IncCol( -nCutOff ); + else + pCutOff->GetBigRange().aStart.IncCol( -nCutOff ); + break; + case SC_CAT_INSERT_ROWS : + if ( nCutOff < 0 ) + pCutOff->GetBigRange().aEnd.IncRow( -nCutOff ); + else + pCutOff->GetBigRange().aStart.IncRow( -nCutOff ); + break; + case SC_CAT_INSERT_TABS : + if ( nCutOff < 0 ) + pCutOff->GetBigRange().aEnd.IncTab( -nCutOff ); + else + pCutOff->GetBigRange().aStart.IncTab( -nCutOff ); + break; + default: + { + // added to avoid warnings + } + } + SetCutOffInsert( nullptr, 0 ); +} + +// ScChangeActionMove +ScChangeActionMove::ScChangeActionMove( + const sal_uLong nActionNumber, const ScChangeActionState eStateP, + const sal_uLong nRejectingNumber, const ScBigRange& aToBigRange, + const OUString& aUserP, const DateTime& aDateTimeP, + const OUString &sComment, ScBigRange aFromBigRange, + ScChangeTrack* pTrackP) : // which of nDx and nDy is set depends on the type + ScChangeAction(SC_CAT_MOVE, aToBigRange, nActionNumber, nRejectingNumber, eStateP, aDateTimeP, aUserP, sComment), + aFromRange(std::move(aFromBigRange)), + pTrack( pTrackP ), + nStartLastCut(0), + nEndLastCut(0) +{ +} + +ScChangeActionMove::~ScChangeActionMove() +{ + DeleteCellEntries(); +} + +void ScChangeActionMove::AddContent( ScChangeActionContent* pContent ) +{ + mvCells.push_back(pContent); +} + +void ScChangeActionMove::DeleteCellEntries() +{ + pTrack->DeleteCellEntries( mvCells, this ); +} + +void ScChangeActionMove::UpdateReference( const ScChangeTrack* /* pTrack */, + UpdateRefMode eMode, const ScBigRange& rRange, + sal_Int32 nDx, sal_Int32 nDy, sal_Int32 nDz ) +{ + ScRefUpdate::Update( eMode, rRange, nDx, nDy, nDz, aFromRange ); + ScRefUpdate::Update( eMode, rRange, nDx, nDy, nDz, GetBigRange() ); +} + +void ScChangeActionMove::GetDelta( sal_Int32& nDx, sal_Int32& nDy, sal_Int32& nDz ) const +{ + const ScBigAddress& rToPos = GetBigRange().aStart; + const ScBigAddress& rFromPos = GetFromRange().aStart; + nDx = rToPos.Col() - rFromPos.Col(); + nDy = rToPos.Row() - rFromPos.Row(); + nDz = rToPos.Tab() - rFromPos.Tab(); +} + +OUString ScChangeActionMove::GetDescription( + ScDocument& rDoc, bool bSplitRange, bool bWarning ) const +{ + OUString str = ScChangeAction::GetDescription( rDoc, bSplitRange, bWarning ); + + bool bFlag3D = GetFromRange().aStart.Tab() != GetBigRange().aStart.Tab(); + + OUString aRsc = ScResId(STR_CHANGED_MOVE); + + OUString aTmpStr = ScChangeAction::GetRefString(GetFromRange(), rDoc, bFlag3D); + sal_Int32 nPos = aRsc.indexOf("#1"); + if (nPos >= 0) + { + aRsc = aRsc.replaceAt(nPos, 2, aTmpStr); + nPos += aTmpStr.getLength(); + } + + aTmpStr = ScChangeAction::GetRefString(GetBigRange(), rDoc, bFlag3D); + nPos = nPos >= 0 ? aRsc.indexOf("#2", nPos) : -1; + if (nPos >= 0) + { + aRsc = aRsc.replaceAt(nPos, 2, aTmpStr); + } + + return str + aRsc; // append to the original string. +} + +OUString ScChangeActionMove::GetRefString( ScDocument& rDoc, bool bFlag3D ) const +{ + if ( !bFlag3D ) + bFlag3D = ( GetFromRange().aStart.Tab() != GetBigRange().aStart.Tab() ); + + return ScChangeAction::GetRefString(GetFromRange(), rDoc, bFlag3D) + + ", " + + ScChangeAction::GetRefString(GetBigRange(), rDoc, bFlag3D); +} + +bool ScChangeActionMove::Reject( ScDocument& rDoc ) +{ + if ( !(aBigRange.IsValid( rDoc ) && aFromRange.IsValid( rDoc )) ) + return false; + + ScRange aToRange( aBigRange.MakeRange( rDoc ) ); + ScRange aFrmRange( aFromRange.MakeRange( rDoc ) ); + + bool bOk = rDoc.IsBlockEditable( aToRange.aStart.Tab(), + aToRange.aStart.Col(), aToRange.aStart.Row(), + aToRange.aEnd.Col(), aToRange.aEnd.Row() ); + if ( bOk ) + bOk = rDoc.IsBlockEditable( aFrmRange.aStart.Tab(), + aFrmRange.aStart.Col(), aFrmRange.aStart.Row(), + aFrmRange.aEnd.Col(), aFrmRange.aEnd.Row() ); + if ( !bOk ) + return false; + + pTrack->LookUpContents( aToRange, &rDoc, 0, 0, 0 ); // Contents to be moved + + rDoc.DeleteAreaTab( aToRange, InsertDeleteFlags::ALL ); + rDoc.DeleteAreaTab( aFrmRange, InsertDeleteFlags::ALL ); + // Adjust formula in the Document + sc::RefUpdateContext aCxt(rDoc); + aCxt.meMode = URM_MOVE; + aCxt.maRange = aFrmRange; + aCxt.mnColDelta = aFrmRange.aStart.Col() - aToRange.aStart.Col(); + aCxt.mnRowDelta = aFrmRange.aStart.Row() - aToRange.aStart.Row(); + aCxt.mnTabDelta = aFrmRange.aStart.Tab() - aToRange.aStart.Tab(); + rDoc.UpdateReference(aCxt); + + // Free LinkDependent, set succeeding UpdateReference Undo + // ToRange->FromRange Dependents + RemoveAllDependent(); + + // Sets rejected and calls UpdateReference Undo and DeleteCellEntries + RejectRestoreContents( pTrack, 0, 0 ); + + while ( pLinkDependent ) + { + ScChangeAction* p = pLinkDependent->GetAction(); + if ( p && p->GetType() == SC_CAT_CONTENT ) + { + ScChangeActionContent* pContent = static_cast<ScChangeActionContent*>(p); + if ( !pContent->IsDeletedIn() && + pContent->GetBigRange().aStart.IsValid( rDoc ) ) + pContent->PutNewValueToDoc( &rDoc, 0, 0 ); + // Delete the ones created in LookUpContents + if ( pTrack->IsGenerated( pContent->GetActionNumber() ) && + !pContent->IsDeletedIn() ) + { + pLinkDependent->UnLink(); // Else this one is also deleted! + pTrack->DeleteGeneratedDelContent( pContent ); + } + } + delete pLinkDependent; + } + + RemoveAllLinks(); + return true; +} + +ScChangeActionContent::ScChangeActionContent( const ScRange& rRange ) : + ScChangeAction(SC_CAT_CONTENT, rRange), + pNextContent(nullptr), + pPrevContent(nullptr), + pNextInSlot(nullptr), + ppPrevInSlot(nullptr) +{} + +ScChangeActionContent::ScChangeActionContent( const sal_uLong nActionNumber, + const ScChangeActionState eStateP, const sal_uLong nRejectingNumber, + const ScBigRange& aBigRangeP, const OUString& aUserP, + const DateTime& aDateTimeP, const OUString& sComment, + ScCellValue aOldCell, const ScDocument* pDoc, const OUString& sOldValue ) : + ScChangeAction(SC_CAT_CONTENT, aBigRangeP, nActionNumber, nRejectingNumber, eStateP, aDateTimeP, aUserP, sComment), + maOldCell(std::move(aOldCell)), + maOldValue(sOldValue), + pNextContent(nullptr), + pPrevContent(nullptr), + pNextInSlot(nullptr), + ppPrevInSlot(nullptr) +{ + if (!maOldCell.isEmpty()) + SetCell(maOldValue, maOldCell, 0, pDoc); + + if (!sOldValue.isEmpty()) // #i40704# don't overwrite SetCell result with empty string + maOldValue = sOldValue; // set again, because SetCell removes it +} + +ScChangeActionContent::ScChangeActionContent( const sal_uLong nActionNumber, + ScCellValue aNewCell, const ScBigRange& aBigRangeP, + const ScDocument* pDoc, const OUString& sNewValue ) : + ScChangeAction(SC_CAT_CONTENT, aBigRangeP, nActionNumber), + maNewCell(std::move(aNewCell)), + maNewValue(sNewValue), + pNextContent(nullptr), + pPrevContent(nullptr), + pNextInSlot(nullptr), + ppPrevInSlot(nullptr) +{ + if (!maNewCell.isEmpty()) + SetCell(maNewValue, maNewCell, 0, pDoc); + + if (!sNewValue.isEmpty()) // #i40704# don't overwrite SetCell result with empty string + maNewValue = sNewValue; // set again, because SetCell removes it +} + +ScChangeActionContent::~ScChangeActionContent() +{ + ClearTrack(); +} + +void ScChangeActionContent::ClearTrack() +{ + RemoveFromSlot(); + if ( pPrevContent ) + pPrevContent->pNextContent = pNextContent; + if ( pNextContent ) + pNextContent->pPrevContent = pPrevContent; +} + +ScChangeActionContent* ScChangeActionContent::GetTopContent() const +{ + if ( pNextContent ) + { + ScChangeActionContent* pContent = pNextContent; + while ( pContent->pNextContent && pContent != pContent->pNextContent ) + pContent = pContent->pNextContent; + return pContent; + } + return const_cast<ScChangeActionContent*>(this); +} + +ScChangeActionLinkEntry* ScChangeActionContent::GetDeletedIn() const +{ + if ( pNextContent ) + return GetTopContent()->pLinkDeletedIn; + return pLinkDeletedIn; +} + +ScChangeActionLinkEntry** ScChangeActionContent::GetDeletedInAddress() +{ + if ( pNextContent ) + return GetTopContent()->GetDeletedInAddress(); + return &pLinkDeletedIn; +} + +void ScChangeActionContent::SetOldValue( + const ScCellValue& rCell, const ScDocument* pFromDoc, ScDocument* pToDoc, sal_uLong nFormat ) +{ + SetValue(maOldValue, maOldCell, nFormat, rCell, pFromDoc, pToDoc); +} + +void ScChangeActionContent::SetOldValue( + const ScCellValue& rCell, const ScDocument* pFromDoc, ScDocument* pToDoc ) +{ + SetValue(maOldValue, maOldCell, aBigRange.aStart.MakeAddress(*pFromDoc), rCell, pFromDoc, pToDoc); +} + +void ScChangeActionContent::SetNewValue( const ScCellValue& rCell, ScDocument* pDoc ) +{ + SetValue(maNewValue, maNewCell, aBigRange.aStart.MakeAddress(*pDoc), rCell, pDoc, pDoc); +} + +void ScChangeActionContent::SetOldNewCells( + const ScCellValue& rOldCell, sal_uLong nOldFormat, const ScCellValue& rNewCell, + sal_uLong nNewFormat, const ScDocument* pDoc ) +{ + maOldCell = rOldCell; + maNewCell = rNewCell; + SetCell(maOldValue, maOldCell, nOldFormat, pDoc); + SetCell(maNewValue, maNewCell, nNewFormat, pDoc); +} + +void ScChangeActionContent::SetNewCell( + const ScCellValue& rCell, const ScDocument* pDoc, const OUString& rFormatted ) +{ + maNewCell = rCell; + SetCell(maNewValue, maNewCell, 0, pDoc); + + // #i40704# allow to set formatted text here - don't call SetNewValue with string from XML filter + if (!rFormatted.isEmpty()) + maNewValue = rFormatted; +} + +void ScChangeActionContent::SetValueString( + OUString& rValue, ScCellValue& rCell, const OUString& rStr, ScDocument* pDoc ) +{ + rCell.clear(); + if ( rStr.getLength() > 1 && rStr[0] == '=' ) + { + rValue.clear(); + rCell.set(new ScFormulaCell( + *pDoc, aBigRange.aStart.MakeAddress(*pDoc), rStr, + pDoc->GetGrammar() )); + rCell.getFormula()->SetInChangeTrack(true); + } + else + rValue = rStr; +} + +void ScChangeActionContent::SetOldValue( const OUString& rOld, ScDocument* pDoc ) +{ + SetValueString(maOldValue, maOldCell, rOld, pDoc); +} + +OUString ScChangeActionContent::GetOldString( const ScDocument* pDoc ) const +{ + return GetValueString(maOldValue, maOldCell, pDoc); +} + +OUString ScChangeActionContent::GetNewString( const ScDocument* pDoc ) const +{ + return GetValueString(maNewValue, maNewCell, pDoc); +} + +OUString ScChangeActionContent::GetDescription( + ScDocument& rDoc, bool bSplitRange, bool bWarning ) const +{ + OUString str = ScChangeAction::GetDescription( rDoc, bSplitRange, bWarning ); + + OUString aRsc = ScResId(STR_CHANGED_CELL); + + OUString aTmpStr = GetRefString(rDoc); + + sal_Int32 nPos = aRsc.indexOf("#1", 0); + if (nPos >= 0) + { + aRsc = aRsc.replaceAt(nPos, 2, aTmpStr); + nPos += aTmpStr.getLength(); + } + + aTmpStr = GetOldString( &rDoc ); + if (aTmpStr.isEmpty()) + aTmpStr = ScResId( STR_CHANGED_BLANK ); + + nPos = nPos >= 0 ? aRsc.indexOf("#2", nPos) : -1; + if (nPos >= 0) + { + aRsc = aRsc.replaceAt(nPos, 2, aTmpStr); + nPos += aTmpStr.getLength(); + } + + aTmpStr = GetNewString( &rDoc ); + if (aTmpStr.isEmpty()) + aTmpStr = ScResId( STR_CHANGED_BLANK ); + + nPos = nPos >= 0 ? aRsc.indexOf("#3", nPos) : -1; + if (nPos >= 0) + { + aRsc = aRsc.replaceAt(nPos, 2, aTmpStr); + } + + return str + aRsc; // append to the original string. +} + +OUString ScChangeActionContent::GetRefString( + ScDocument& rDoc, bool bFlag3D ) const +{ + ScRefFlags nFlags = ( GetBigRange().IsValid( rDoc ) ? ScRefFlags::VALID : ScRefFlags::ZERO ); + if ( nFlags != ScRefFlags::ZERO ) + { + const ScCellValue& rCell = GetNewCell(); + if ( GetContentCellType(rCell) == SC_CACCT_MATORG ) + { + ScBigRange aLocalBigRange( GetBigRange() ); + SCCOL nC; + SCROW nR; + rCell.getFormula()->GetMatColsRows( nC, nR ); + aLocalBigRange.aEnd.IncCol( nC-1 ); + aLocalBigRange.aEnd.IncRow( nR-1 ); + return ScChangeAction::GetRefString( aLocalBigRange, rDoc, bFlag3D ); + } + + ScAddress aTmpAddress( GetBigRange().aStart.MakeAddress( rDoc ) ); + if ( bFlag3D ) + nFlags |= ScRefFlags::TAB_3D; + OUString str = aTmpAddress.Format(nFlags, &rDoc, rDoc.GetAddressConvention()); + if ( IsDeletedIn() ) + { + // Insert the parentheses. + str = "(" + str + ")"; + } + return str; + } + else + return ScCompiler::GetNativeSymbol(ocErrRef); +} + +bool ScChangeActionContent::Reject( ScDocument& rDoc ) +{ + if ( !aBigRange.IsValid( rDoc ) ) + return false; + + PutOldValueToDoc( &rDoc, 0, 0 ); + + SetState( SC_CAS_REJECTED ); + RemoveAllLinks(); + + return true; +} + +bool ScChangeActionContent::Select( ScDocument& rDoc, ScChangeTrack* pTrack, + bool bOldest, ::std::stack<ScChangeActionContent*>* pRejectActions ) +{ + if ( !aBigRange.IsValid( rDoc ) ) + return false; + + ScChangeActionContent* pContent = this; + // accept previous contents + while ( ( pContent = pContent->pPrevContent ) != nullptr ) + { + if ( pContent->IsVirgin() ) + pContent->SetState( SC_CAS_ACCEPTED ); + } + ScChangeActionContent* pEnd = pContent = this; + // reject subsequent contents + while ( ( pContent = pContent->pNextContent ) != nullptr ) + { + // MatrixOrigin may have dependents, no dependency recursion needed + const ScChangeActionLinkEntry* pL = pContent->GetFirstDependentEntry(); + while ( pL ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pL->GetAction()); + if ( p ) + p->SetRejected(); + pL = pL->GetNext(); + } + pContent->SetRejected(); + pEnd = pContent; + } + + // If not oldest: Is it anyone else than the last one? + if ( bOldest || pEnd != this ) + { ScRange aRange( aBigRange.aStart.MakeAddress( rDoc ) ); + const ScAddress& rPos = aRange.aStart; + + ScChangeActionContent* pNew = new ScChangeActionContent( aRange ); + ScCellValue aCell; + aCell.assign(rDoc, rPos); + pNew->SetOldValue(aCell, &rDoc, &rDoc); + + if ( bOldest ) + PutOldValueToDoc( &rDoc, 0, 0 ); + else + PutNewValueToDoc( &rDoc, 0, 0 ); + + pNew->SetRejectAction( bOldest ? GetActionNumber() : pEnd->GetActionNumber() ); + pNew->SetState( SC_CAS_ACCEPTED ); + if ( pRejectActions ) + pRejectActions->push( pNew ); + else + { + aCell.assign(rDoc, rPos); + pNew->SetNewValue(aCell, &rDoc); + pTrack->Append( pNew ); + } + } + + if ( bOldest ) + SetRejected(); + else + SetState( SC_CAS_ACCEPTED ); + + return true; +} + +OUString ScChangeActionContent::GetStringOfCell( + const ScCellValue& rCell, const ScDocument* pDoc, const ScAddress& rPos ) +{ + if (NeedsNumberFormat(rCell)) + return GetStringOfCell(rCell, pDoc, pDoc->GetNumberFormat(rPos)); + else + return GetStringOfCell(rCell, pDoc, 0); +} + +OUString ScChangeActionContent::GetStringOfCell( + const ScCellValue& rCell, const ScDocument* pDoc, sal_uLong nFormat ) +{ + if (!GetContentCellType(rCell)) + return OUString(); + + switch (rCell.getType()) + { + case CELLTYPE_VALUE: + { + OUString str; + pDoc->GetFormatTable()->GetInputLineString(rCell.getDouble(), nFormat, str); + return str; + } + case CELLTYPE_STRING: + return rCell.getSharedString()->getString(); + case CELLTYPE_EDIT: + if (rCell.getEditText()) + return ScEditUtil::GetString(*rCell.getEditText(), pDoc); + return OUString(); + case CELLTYPE_FORMULA: + return rCell.getFormula()->GetFormula(); + default: + return OUString(); + } +} + +ScChangeActionContentCellType ScChangeActionContent::GetContentCellType( const ScCellValue& rCell ) +{ + switch (rCell.getType()) + { + case CELLTYPE_VALUE : + case CELLTYPE_STRING : + case CELLTYPE_EDIT : + return SC_CACCT_NORMAL; + case CELLTYPE_FORMULA : + switch (rCell.getFormula()->GetMatrixFlag()) + { + case ScMatrixMode::NONE : + return SC_CACCT_NORMAL; + case ScMatrixMode::Formula : + return SC_CACCT_MATORG; + case ScMatrixMode::Reference : + return SC_CACCT_MATREF; + } + return SC_CACCT_NORMAL; + default: + return SC_CACCT_NONE; + } +} + +ScChangeActionContentCellType ScChangeActionContent::GetContentCellType( const ScRefCellValue& rCell ) +{ + switch (rCell.getType()) + { + case CELLTYPE_VALUE: + case CELLTYPE_STRING: + case CELLTYPE_EDIT: + return SC_CACCT_NORMAL; + case CELLTYPE_FORMULA: + { + const ScFormulaCell* pCell = rCell.getFormula(); + switch (pCell->GetMatrixFlag()) + { + case ScMatrixMode::NONE : + return SC_CACCT_NORMAL; + case ScMatrixMode::Formula : + return SC_CACCT_MATORG; + case ScMatrixMode::Reference : + return SC_CACCT_MATREF; + } + return SC_CACCT_NORMAL; + } + default: + ; + } + + return SC_CACCT_NONE; +} + +bool ScChangeActionContent::NeedsNumberFormat( const ScCellValue& rVal ) +{ + return rVal.getType() == CELLTYPE_VALUE; +} + +void ScChangeActionContent::SetValue( + OUString& rStr, ScCellValue& rCell, const ScAddress& rPos, const ScCellValue& rOrgCell, + const ScDocument* pFromDoc, ScDocument* pToDoc ) +{ + sal_uInt32 nFormat = NeedsNumberFormat(rOrgCell) ? pFromDoc->GetNumberFormat(rPos) : 0; + SetValue(rStr, rCell, nFormat, rOrgCell, pFromDoc, pToDoc); +} + +void ScChangeActionContent::SetValue( + OUString& rStr, ScCellValue& rCell, sal_uLong nFormat, const ScCellValue& rOrgCell, + const ScDocument* pFromDoc, ScDocument* pToDoc ) +{ + rStr.clear(); + + if (GetContentCellType(rOrgCell)) + { + rCell.assign(rOrgCell, *pToDoc); + switch (rOrgCell.getType()) + { + case CELLTYPE_VALUE : + { // E.g.: Remember date as such + pFromDoc->GetFormatTable()->GetInputLineString( + rOrgCell.getDouble(), nFormat, rStr); + } + break; + case CELLTYPE_FORMULA : + rCell.getFormula()->SetInChangeTrack(true); + break; + default: + { + // added to avoid warnings + } + } + } + else + rCell.clear(); +} + +void ScChangeActionContent::SetCell( OUString& rStr, ScCellValue& rCell, sal_uLong nFormat, const ScDocument* pDoc ) +{ + rStr.clear(); + if (rCell.isEmpty()) + return; + + switch (rCell.getType()) + { + case CELLTYPE_VALUE : + // e.g. remember date as date string + pDoc->GetFormatTable()->GetInputLineString(rCell.getDouble(), nFormat, rStr); + break; + case CELLTYPE_FORMULA : + rCell.getFormula()->SetInChangeTrack(true); + break; + default: + { + // added to avoid warnings + } + } +} + +OUString ScChangeActionContent::GetValueString( + const OUString& rValue, const ScCellValue& rCell, const ScDocument* pDoc ) const +{ + if (!rValue.isEmpty()) + { + return rValue; + } + + switch (rCell.getType()) + { + case CELLTYPE_STRING : + return rCell.getSharedString()->getString(); + case CELLTYPE_EDIT : + if (rCell.getEditText()) + return ScEditUtil::GetString(*rCell.getEditText(), pDoc); + return OUString(); + case CELLTYPE_VALUE : // Is always in rValue + return rValue; + case CELLTYPE_FORMULA : + return GetFormulaString(rCell.getFormula()); + case CELLTYPE_NONE: + default: + return OUString(); + } +} + +OUString ScChangeActionContent::GetFormulaString( + const ScFormulaCell* pCell ) const +{ + ScAddress aPos( aBigRange.aStart.MakeAddress( pCell->GetDocument()) ); + if ( aPos == pCell->aPos || IsDeletedIn() ) + return pCell->GetFormula(); + else + { + OSL_FAIL( "ScChangeActionContent::GetFormulaString: aPos != pCell->aPos" ); + ScFormulaCell aNew( *pCell, pCell->GetDocument(), aPos ); + return aNew.GetFormula(); + } +} + +void ScChangeActionContent::PutOldValueToDoc( ScDocument* pDoc, + SCCOL nDx, SCROW nDy ) const +{ + PutValueToDoc(maOldCell, maOldValue, pDoc, nDx, nDy); +} + +void ScChangeActionContent::PutNewValueToDoc( ScDocument* pDoc, + SCCOL nDx, SCROW nDy ) const +{ + PutValueToDoc(maNewCell, maNewValue, pDoc, nDx, nDy); +} + +void ScChangeActionContent::PutValueToDoc( + const ScCellValue& rCell, const OUString& rValue, ScDocument* pDoc, + SCCOL nDx, SCROW nDy ) const +{ + ScAddress aPos( aBigRange.aStart.MakeAddress( *pDoc ) ); + if ( nDx ) + aPos.IncCol( nDx ); + if ( nDy ) + aPos.IncRow( nDy ); + + if (!rValue.isEmpty()) + { + pDoc->SetString(aPos, rValue); + return; + } + + if (rCell.isEmpty()) + { + pDoc->SetEmptyCell(aPos); + return; + } + + if (rCell.getType() == CELLTYPE_VALUE) + { + pDoc->SetString( aPos.Col(), aPos.Row(), aPos.Tab(), rValue ); + return; + } + + switch (GetContentCellType(rCell)) + { + case SC_CACCT_MATORG : + { + SCCOL nC; + SCROW nR; + rCell.getFormula()->GetMatColsRows(nC, nR); + OSL_ENSURE( nC>0 && nR>0, "ScChangeActionContent::PutValueToDoc: MatColsRows?" ); + ScRange aRange( aPos ); + if ( nC > 1 ) + aRange.aEnd.IncCol( nC-1 ); + if ( nR > 1 ) + aRange.aEnd.IncRow( nR-1 ); + ScMarkData aDestMark(pDoc->GetSheetLimits()); + aDestMark.SelectOneTable( aPos.Tab() ); + aDestMark.SetMarkArea( aRange ); + pDoc->InsertMatrixFormula( aPos.Col(), aPos.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row(), + aDestMark, OUString(), rCell.getFormula()->GetCode()); + } + break; + case SC_CACCT_MATREF : + // nothing + break; + default: + rCell.commit(*pDoc, aPos); + } +} + +static void lcl_InvalidateReference( const ScDocument& rDoc, formula::FormulaToken& rTok, const ScBigAddress& rPos ) +{ + ScSingleRefData& rRef1 = *rTok.GetSingleRef(); + if ( rPos.Col() < 0 || rDoc.MaxCol() < rPos.Col() ) + { + rRef1.SetColDeleted( true ); + } + if ( rPos.Row() < 0 || rDoc.MaxRow() < rPos.Row() ) + { + rRef1.SetRowDeleted( true ); + } + if ( rPos.Tab() < 0 || MAXTAB < rPos.Tab() ) + { + rRef1.SetTabDeleted( true ); + } + if ( rTok.GetType() != formula::svDoubleRef ) + return; + + ScSingleRefData& rRef2 = rTok.GetDoubleRef()->Ref2; + if ( rPos.Col() < 0 || rDoc.MaxCol() < rPos.Col() ) + { + rRef2.SetColDeleted( true ); + } + if ( rPos.Row() < 0 || rDoc.MaxRow() < rPos.Row() ) + { + rRef2.SetRowDeleted( true ); + } + if ( rPos.Tab() < 0 || MAXTAB < rPos.Tab() ) + { + rRef2.SetTabDeleted( true ); + } +} + +void ScChangeActionContent::UpdateReference( const ScChangeTrack* pTrack, + UpdateRefMode eMode, const ScBigRange& rRange, + sal_Int32 nDx, sal_Int32 nDy, sal_Int32 nDz ) +{ + SCSIZE nOldSlot = pTrack->ComputeContentSlot( aBigRange.aStart.Row() ); + ScRefUpdate::Update( eMode, rRange, nDx, nDy, nDz, aBigRange ); + SCSIZE nNewSlot = pTrack->ComputeContentSlot( aBigRange.aStart.Row() ); + if ( nNewSlot != nOldSlot ) + { + RemoveFromSlot(); + InsertInSlot( &(pTrack->GetContentSlots()[nNewSlot]) ); + } + + if ( pTrack->IsInDelete() && !pTrack->IsInDeleteTop() ) + return ; // Formula only update whole range + + bool bOldFormula = maOldCell.getType() == CELLTYPE_FORMULA; + bool bNewFormula = maNewCell.getType() == CELLTYPE_FORMULA; + if ( !(bOldFormula || bNewFormula) ) + return; + +// Adjust UpdateReference via ScFormulaCell (there) + if ( pTrack->IsInDelete() ) + { + const ScRange& rDelRange = pTrack->GetInDeleteRange(); + if ( nDx > 0 ) + nDx = rDelRange.aEnd.Col() - rDelRange.aStart.Col() + 1; + else if ( nDx < 0 ) + nDx = -(rDelRange.aEnd.Col() - rDelRange.aStart.Col() + 1); + if ( nDy > 0 ) + nDy = rDelRange.aEnd.Row() - rDelRange.aStart.Row() + 1; + else if ( nDy < 0 ) + nDy = -(rDelRange.aEnd.Row() - rDelRange.aStart.Row() + 1); + if ( nDz > 0 ) + nDz = rDelRange.aEnd.Tab() - rDelRange.aStart.Tab() + 1; + else if ( nDz < 0 ) + nDz = -(rDelRange.aEnd.Tab() - rDelRange.aStart.Tab() + 1); + } + ScBigRange aTmpRange( rRange ); + switch ( eMode ) + { + case URM_INSDEL : + if ( nDx < 0 || nDy < 0 || nDz < 0 ) + { // Delete starts there after removed range + // Position is changed there + if ( nDx ) + aTmpRange.aStart.IncCol( -nDx ); + if ( nDy ) + aTmpRange.aStart.IncRow( -nDy ); + if ( nDz ) + aTmpRange.aStart.IncTab( -nDz ); + } + break; + case URM_MOVE : + // Move is Source here and Target there + // Position needs to be adjusted before that + if ( bOldFormula ) + maOldCell.getFormula()->aPos = aBigRange.aStart.MakeAddress(pTrack->GetDocument()); + if ( bNewFormula ) + maNewCell.getFormula()->aPos = aBigRange.aStart.MakeAddress(pTrack->GetDocument()); + if ( nDx ) + { + aTmpRange.aStart.IncCol( nDx ); + aTmpRange.aEnd.IncCol( nDx ); + } + if ( nDy ) + { + aTmpRange.aStart.IncRow( nDy ); + aTmpRange.aEnd.IncRow( nDy ); + } + if ( nDz ) + { + aTmpRange.aStart.IncTab( nDz ); + aTmpRange.aEnd.IncTab( nDz ); + } + break; + default: + { + // added to avoid warnings + } + } + ScRange aRange( aTmpRange.MakeRange(pTrack->GetDocument()) ); + + sc::RefUpdateContext aRefCxt(pTrack->GetDocument()); + aRefCxt.meMode = eMode; + aRefCxt.maRange = aRange; + aRefCxt.mnColDelta = nDx; + aRefCxt.mnRowDelta = nDy; + aRefCxt.mnTabDelta = nDz; + + if ( bOldFormula ) + maOldCell.getFormula()->UpdateReference(aRefCxt); + if ( bNewFormula ) + maNewCell.getFormula()->UpdateReference(aRefCxt); + + if ( aBigRange.aStart.IsValid( pTrack->GetDocument() ) ) + return; + +//FIXME: + // UpdateReference cannot handle positions outside of the Document. + // Therefore set everything to #REF! + //TODO: Remove the need for this hack! This means big changes to ScAddress etc.! + const ScBigAddress& rPos = aBigRange.aStart; + if ( bOldFormula ) + { + formula::FormulaToken* t; + ScTokenArray* pArr = maOldCell.getFormula()->GetCode(); + formula::FormulaTokenArrayPlainIterator aIter(*pArr); + while ( ( t = aIter.GetNextReference() ) != nullptr ) + lcl_InvalidateReference( pTrack->GetDocument(), *t, rPos ); + aIter.Reset(); + while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr ) + lcl_InvalidateReference( pTrack->GetDocument(), *t, rPos ); + } + if ( bNewFormula ) + { + formula::FormulaToken* t; + ScTokenArray* pArr = maNewCell.getFormula()->GetCode(); + formula::FormulaTokenArrayPlainIterator aIter(*pArr); + while ( ( t = aIter.GetNextReference() ) != nullptr ) + lcl_InvalidateReference( pTrack->GetDocument(), *t, rPos ); + aIter.Reset(); + while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr ) + lcl_InvalidateReference( pTrack->GetDocument(), *t, rPos ); + } +} + +bool ScChangeActionContent::IsMatrixOrigin() const +{ + return GetContentCellType(GetNewCell()) == SC_CACCT_MATORG; +} + +bool ScChangeActionContent::IsOldMatrixReference() const +{ + return GetContentCellType(GetOldCell()) == SC_CACCT_MATREF; +} + +// ScChangeActionReject +ScChangeActionReject::ScChangeActionReject( + const sal_uLong nActionNumber, const ScChangeActionState eStateP, + const sal_uLong nRejectingNumber, + const ScBigRange& aBigRangeP, const OUString& aUserP, + const DateTime& aDateTimeP, const OUString& sComment) : + ScChangeAction(SC_CAT_CONTENT, aBigRangeP, nActionNumber, nRejectingNumber, eStateP, aDateTimeP, aUserP, sComment) +{ +} + +bool ScChangeActionReject::Reject(ScDocument& /*rDoc*/) +{ + return false; +} + +SCSIZE ScChangeTrack::ComputeContentSlot( sal_Int32 nRow ) const +{ + if ( nRow < 0 || nRow > rDoc.GetSheetLimits().mnMaxRow ) + return mnContentSlots - 1; + return static_cast< SCSIZE >( nRow / mnContentRowsPerSlot ); +} + +SCROW ScChangeTrack::InitContentRowsPerSlot() +{ + const SCSIZE nMaxSlots = 0xffe0 / sizeof( ScChangeActionContent* ) - 2; + SCROW nRowsPerSlot = rDoc.GetMaxRowCount() / nMaxSlots; + if ( nRowsPerSlot * nMaxSlots < sal::static_int_cast<SCSIZE>(rDoc.GetMaxRowCount()) ) + ++nRowsPerSlot; + return nRowsPerSlot; +} + +ScChangeTrack::ScChangeTrack( ScDocument& rDocP ) : + aFixDateTime( DateTime::SYSTEM ), + rDoc( rDocP ) +{ + Init(); + SC_MOD()->GetUserOptions().AddListener(this); + + ppContentSlots.reset( new ScChangeActionContent* [ mnContentSlots ] ); + memset( ppContentSlots.get(), 0, mnContentSlots * sizeof( ScChangeActionContent* ) ); +} + +ScChangeTrack::ScChangeTrack( ScDocument& rDocP, std::set<OUString>&& aTempUserCollection) : + maUserCollection(std::move(aTempUserCollection)), + aFixDateTime( DateTime::SYSTEM ), + rDoc( rDocP ) +{ + Init(); + SC_MOD()->GetUserOptions().AddListener(this); + ppContentSlots.reset( new ScChangeActionContent* [ mnContentSlots ] ); + memset( ppContentSlots.get(), 0, mnContentSlots * sizeof( ScChangeActionContent* ) ); +} + +ScChangeTrack::~ScChangeTrack() +{ + SC_MOD()->GetUserOptions().RemoveListener(this); + DtorClear(); +} + +void ScChangeTrack::Init() +{ + mnContentRowsPerSlot = InitContentRowsPerSlot(); + mnContentSlots = rDoc.GetMaxRowCount() / InitContentRowsPerSlot() + 2; + + pFirst = nullptr; + pLast = nullptr; + pFirstGeneratedDelContent = nullptr; + pLastCutMove = nullptr; + pLinkInsertCol = nullptr; + pLinkInsertRow = nullptr; + pLinkInsertTab = nullptr; + pLinkMove = nullptr; + xBlockModifyMsg.reset(); + nActionMax = 0; + nGeneratedMin = SC_CHGTRACK_GENERATED_START; + nMarkLastSaved = 0; + nStartLastCut = 0; + nEndLastCut = 0; + nLastMerge = 0; + eMergeState = SC_CTMS_NONE; + bInDelete = false; + bInDeleteTop = false; + bInDeleteUndo = false; + bInPasteCut = false; + bUseFixDateTime = false; + bTimeNanoSeconds = true; + + CreateAuthorName(); +} + +void ScChangeTrack::DtorClear() +{ + ScChangeAction* p; + ScChangeAction* pNext; + for ( p = GetFirst(); p; p = pNext ) + { + pNext = p->GetNext(); + delete p; + } + for ( p = pFirstGeneratedDelContent; p; p = pNext ) + { + pNext = p->GetNext(); + delete p; + } + for( const auto& rEntry : aPasteCutMap ) + { + delete rEntry.second; + } + pLastCutMove.reset(); + ClearMsgQueue(); +} + +void ScChangeTrack::ClearMsgQueue() +{ + xBlockModifyMsg.reset(); + aMsgStackTmp.clear(); + aMsgStackFinal.clear(); + aMsgQueue.clear(); +} + +void ScChangeTrack::Clear() +{ + DtorClear(); + aMap.clear(); + aGeneratedMap.clear(); + aPasteCutMap.clear(); + maUserCollection.clear(); + maUser.clear(); + Init(); +} + +bool ScChangeTrack::IsGenerated( sal_uLong nAction ) const +{ + return nAction >= nGeneratedMin; +} + +ScChangeAction* ScChangeTrack::GetAction( sal_uLong nAction ) const +{ + ScChangeActionMap::const_iterator it = aMap.find( nAction ); + if( it != aMap.end() ) + return it->second; + else + return nullptr; +} + +ScChangeAction* ScChangeTrack::GetGenerated( sal_uLong nGenerated ) const +{ + ScChangeActionMap::const_iterator it = aGeneratedMap.find( nGenerated ); + if( it != aGeneratedMap.end() ) + return it->second; + else + return nullptr; +} + +ScChangeAction* ScChangeTrack::GetActionOrGenerated( sal_uLong nAction ) const +{ + return IsGenerated( nAction ) ? + GetGenerated( nAction ) : + GetAction( nAction ); +} +sal_uLong ScChangeTrack::GetLastSavedActionNumber() const +{ + return nMarkLastSaved; +} + +void ScChangeTrack::SetLastSavedActionNumber(sal_uLong nNew) +{ + nMarkLastSaved = nNew; +} + +ScChangeAction* ScChangeTrack::GetLastSaved() const +{ + ScChangeActionMap::const_iterator it = aMap.find( nMarkLastSaved ); + if( it != aMap.end() ) + return it->second; + else + return nullptr; +} + +void ScChangeTrack::ConfigurationChanged( utl::ConfigurationBroadcaster*, ConfigurationHints ) +{ + if ( rDoc.IsInDtorClear() ) + return; + + size_t nOldCount = maUserCollection.size(); + + CreateAuthorName(); + + if ( maUserCollection.size() != nOldCount ) + { + // New user in collection -> have to repaint because + // colors may be different now (#106697#). + // (Has to be done in the Notify handler, to be sure + // the user collection has already been updated) + + ScDocShell* pDocSh = rDoc.GetDocumentShell(); + if (pDocSh) + pDocSh->Broadcast( ScPaintHint( ScRange(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB), PaintPartFlags::Grid ) ); + } +} + +void ScChangeTrack::CreateAuthorName() +{ + const SvtUserOptions& rUserOptions = SC_MOD()->GetUserOptions(); + OUString aFirstName(rUserOptions.GetFirstName()); + OUString aLastName(rUserOptions.GetLastName()); + if (aFirstName.isEmpty() && aLastName.isEmpty()) + SetUser(ScResId(STR_CHG_UNKNOWN_AUTHOR)); + else if(!aFirstName.isEmpty() && aLastName.isEmpty()) + SetUser(aFirstName); + else if(aFirstName.isEmpty() && !aLastName.isEmpty()) + SetUser(aLastName); + else + SetUser(aFirstName + " " + aLastName); +} + + +void ScChangeTrack::SetUser( const OUString& rUser ) +{ + maUser = rUser; + maUserCollection.insert(maUser); +} + +void ScChangeTrack::StartBlockModify( ScChangeTrackMsgType eMsgType, + sal_uLong nStartAction ) +{ + if ( aModifiedLink.IsSet() ) + { + if ( xBlockModifyMsg ) + aMsgStackTmp.push_back( *xBlockModifyMsg ); // Block in Block + xBlockModifyMsg = ScChangeTrackMsgInfo(); + xBlockModifyMsg->eMsgType = eMsgType; + xBlockModifyMsg->nStartAction = nStartAction; + xBlockModifyMsg->nEndAction = 0; + } +} + +void ScChangeTrack::EndBlockModify( sal_uLong nEndAction ) +{ + if ( !aModifiedLink.IsSet() ) + return; + + if ( xBlockModifyMsg ) + { + if ( xBlockModifyMsg->nStartAction <= nEndAction ) + { + xBlockModifyMsg->nEndAction = nEndAction; + // Blocks dissolved in Blocks + aMsgStackFinal.push_back( *xBlockModifyMsg ); + } + else + xBlockModifyMsg.reset(); + if (aMsgStackTmp.empty()) + xBlockModifyMsg.reset(); + else + { + xBlockModifyMsg = aMsgStackTmp.back(); // Maybe Block in Block + aMsgStackTmp.pop_back(); + } + } + if ( !xBlockModifyMsg ) + { + bool bNew = !aMsgStackFinal.empty(); + aMsgQueue.reserve(aMsgQueue.size() + aMsgStackFinal.size()); + aMsgQueue.insert(aMsgQueue.end(), aMsgStackFinal.rbegin(), aMsgStackFinal.rend()); + aMsgStackFinal.clear(); + if ( bNew ) + aModifiedLink.Call( *this ); + } +} + +ScChangeTrackMsgQueue& ScChangeTrack::GetMsgQueue() +{ + return aMsgQueue; +} + +void ScChangeTrack::NotifyModified( ScChangeTrackMsgType eMsgType, + sal_uLong nStartAction, sal_uLong nEndAction ) +{ + if ( aModifiedLink.IsSet() ) + { + if ( !xBlockModifyMsg || xBlockModifyMsg->eMsgType != eMsgType || + (IsGenerated( nStartAction ) && + (eMsgType == ScChangeTrackMsgType::Append || eMsgType == ScChangeTrackMsgType::Remove)) ) + { // Append within Append e.g. not + StartBlockModify( eMsgType, nStartAction ); + EndBlockModify( nEndAction ); + } + } +} + +void ScChangeTrack::MasterLinks( ScChangeAction* pAppend ) +{ + ScChangeActionType eType = pAppend->GetType(); + + if ( eType == SC_CAT_CONTENT ) + { + if ( !IsGenerated( pAppend->GetActionNumber() ) ) + { + SCSIZE nSlot = ComputeContentSlot( + pAppend->GetBigRange().aStart.Row() ); + static_cast<ScChangeActionContent*>(pAppend)->InsertInSlot( + &ppContentSlots[nSlot] ); + } + return ; + } + + if ( pAppend->IsRejecting() ) + return ; // Rejects do not have dependencies + + switch ( eType ) + { + case SC_CAT_INSERT_COLS : + { + ScChangeActionLinkEntry* pLink = new ScChangeActionLinkEntry( + &pLinkInsertCol, pAppend ); + pAppend->AddLink( nullptr, pLink ); + } + break; + case SC_CAT_INSERT_ROWS : + { + ScChangeActionLinkEntry* pLink = new ScChangeActionLinkEntry( + &pLinkInsertRow, pAppend ); + pAppend->AddLink( nullptr, pLink ); + } + break; + case SC_CAT_INSERT_TABS : + { + ScChangeActionLinkEntry* pLink = new ScChangeActionLinkEntry( + &pLinkInsertTab, pAppend ); + pAppend->AddLink( nullptr, pLink ); + } + break; + case SC_CAT_MOVE : + { + ScChangeActionLinkEntry* pLink = new ScChangeActionLinkEntry( + &pLinkMove, pAppend ); + pAppend->AddLink( nullptr, pLink ); + } + break; + default: + { + // added to avoid warnings + } + } +} + +void ScChangeTrack::AppendLoaded( std::unique_ptr<ScChangeAction> pActionParam ) +{ + ScChangeAction* pAppend = pActionParam.release(); + aMap.insert( ::std::make_pair( pAppend->GetActionNumber(), pAppend ) ); + if ( !pLast ) + pFirst = pLast = pAppend; + else + { + pLast->pNext = pAppend; + pAppend->pPrev = pLast; + pLast = pAppend; + } + MasterLinks( pAppend ); +} + +void ScChangeTrack::Append( ScChangeAction* pAppend, sal_uLong nAction ) +{ + if ( nActionMax < nAction ) + nActionMax = nAction; + pAppend->SetUser( maUser ); + if ( bUseFixDateTime ) + pAppend->SetDateTimeUTC( aFixDateTime ); + pAppend->SetActionNumber( nAction ); + aMap.insert( ::std::make_pair( nAction, pAppend ) ); + // UpdateReference Inserts before Dependencies. + // Delete rejecting Insert which had UpdateReference with Delete Undo. + // UpdateReference also with pLast==NULL, as pAppend can be a Delete, + // which could have generated DelContents. + if ( pAppend->IsInsertType() && !pAppend->IsRejecting() ) + UpdateReference( pAppend, false ); + if ( !pLast ) + pFirst = pLast = pAppend; + else + { + pLast->pNext = pAppend; + pAppend->pPrev = pLast; + pLast = pAppend; + Dependencies( pAppend ); + } + // UpdateReference does not Insert() after Dependencies. + // Move rejecting Move, which had UpdateReference with Move Undo. + // Do not delete content in ToRange. + if ( !pAppend->IsInsertType() && + !(pAppend->GetType() == SC_CAT_MOVE && pAppend->IsRejecting()) ) + UpdateReference( pAppend, false ); + MasterLinks( pAppend ); + + if ( !aModifiedLink.IsSet() ) + return; + + NotifyModified( ScChangeTrackMsgType::Append, nAction, nAction ); + if ( pAppend->GetType() == SC_CAT_CONTENT ) + { + ScChangeActionContent* pContent = static_cast<ScChangeActionContent*>(pAppend); + if ( ( pContent = pContent->GetPrevContent() ) != nullptr ) + { + sal_uLong nMod = pContent->GetActionNumber(); + NotifyModified( ScChangeTrackMsgType::Change, nMod, nMod ); + } + } + else + NotifyModified( ScChangeTrackMsgType::Change, pFirst->GetActionNumber(), + pLast->GetActionNumber() ); +} + +void ScChangeTrack::Append( ScChangeAction* pAppend ) +{ + Append( pAppend, ++nActionMax ); +} + +void ScChangeTrack::AppendDeleteRange( const ScRange& rRange, + ScDocument* pRefDoc, sal_uLong& nStartAction, sal_uLong& nEndAction, SCTAB nDz ) +{ + nStartAction = GetActionMax() + 1; + AppendDeleteRange( rRange, pRefDoc, nDz, 0 ); + nEndAction = GetActionMax(); +} + +void ScChangeTrack::AppendDeleteRange( const ScRange& rRange, + ScDocument* pRefDoc, SCTAB nDz, sal_uLong nRejectingInsert ) +{ + SetInDeleteRange( rRange ); + StartBlockModify( ScChangeTrackMsgType::Append, GetActionMax() + 1 ); + SCCOL nCol1; + SCROW nRow1; + SCTAB nTab1; + SCCOL nCol2; + SCROW nRow2; + SCTAB nTab2; + rRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + for ( SCTAB nTab = nTab1; nTab <= nTab2; nTab++ ) + { + if ( !pRefDoc || nTab < pRefDoc->GetTableCount() ) + { + if ( nCol1 == 0 && nCol2 == rDoc.MaxCol() ) + { // Whole Row and/or Tables + if ( nRow1 == 0 && nRow2 == rDoc.MaxRow() ) + { // Whole Table + // TODO: Can't we do the whole Table as a whole? + ScRange aRange( 0, 0, nTab, 0, rDoc.MaxRow(), nTab ); + for ( SCCOL nCol = nCol1; nCol <= nCol2; nCol++ ) + { // Column by column is less than row by row + aRange.aStart.SetCol( nCol ); + aRange.aEnd.SetCol( nCol ); + if ( nCol == nCol2 ) + SetInDeleteTop( true ); + AppendOneDeleteRange( aRange, pRefDoc, nCol-nCol1, 0, + nTab-nTab1 + nDz, nRejectingInsert ); + } + // Still InDeleteTop! + AppendOneDeleteRange( rRange, pRefDoc, 0, 0, + nTab-nTab1 + nDz, nRejectingInsert ); + } + else + { // Whole rows + ScRange aRange( 0, 0, nTab, rDoc.MaxCol(), 0, nTab ); + for ( SCROW nRow = nRow1; nRow <= nRow2; nRow++ ) + { + aRange.aStart.SetRow( nRow ); + aRange.aEnd.SetRow( nRow ); + if ( nRow == nRow2 ) + SetInDeleteTop( true ); + AppendOneDeleteRange( aRange, pRefDoc, 0, nRow-nRow1, + 0, nRejectingInsert ); + } + } + } + else if ( nRow1 == 0 && nRow2 == rDoc.MaxRow() ) + { // Whole columns + ScRange aRange( 0, 0, nTab, 0, rDoc.MaxRow(), nTab ); + for ( SCCOL nCol = nCol1; nCol <= nCol2; nCol++ ) + { + aRange.aStart.SetCol( nCol ); + aRange.aEnd.SetCol( nCol ); + if ( nCol == nCol2 ) + SetInDeleteTop( true ); + AppendOneDeleteRange( aRange, pRefDoc, nCol-nCol1, 0, + 0, nRejectingInsert ); + } + } + else + { + OSL_FAIL( "ScChangeTrack::AppendDeleteRange: Block not supported!" ); + } + SetInDeleteTop( false ); + } + } + EndBlockModify( GetActionMax() ); +} + +void ScChangeTrack::AppendOneDeleteRange( const ScRange& rOrgRange, + ScDocument* pRefDoc, SCCOL nDx, SCROW nDy, SCTAB nDz, + sal_uLong nRejectingInsert ) +{ + ScRange aTrackRange( rOrgRange ); + if ( nDx ) + { + aTrackRange.aStart.IncCol( -nDx ); + aTrackRange.aEnd.IncCol( -nDx ); + } + if ( nDy ) + { + aTrackRange.aStart.IncRow( -nDy ); + aTrackRange.aEnd.IncRow( -nDy ); + } + if ( nDz ) + { + aTrackRange.aStart.IncTab( -nDz ); + aTrackRange.aEnd.IncTab( -nDz ); + } + ScChangeActionDel* pAct = new ScChangeActionDel( &rDoc, aTrackRange, nDx, nDy, + this ); + // TabDelete not Contents; they are in separate columns + if ( !(rOrgRange.aStart.Col() == 0 && rOrgRange.aStart.Row() == 0 && + rOrgRange.aEnd.Col() == rDoc.MaxCol() && rOrgRange.aEnd.Row() == rDoc.MaxRow()) ) + LookUpContents( rOrgRange, pRefDoc, -nDx, -nDy, -nDz ); + if ( nRejectingInsert ) + { + pAct->SetRejectAction( nRejectingInsert ); + pAct->SetState( SC_CAS_ACCEPTED ); + } + Append( pAct ); +} + +void ScChangeTrack::LookUpContents( const ScRange& rOrgRange, + ScDocument* pRefDoc, SCCOL nDx, SCROW nDy, SCTAB nDz ) +{ + if (!pRefDoc) + return; + + ScAddress aPos; + ScBigAddress aBigPos; + ScCellIterator aIter( *pRefDoc, rOrgRange ); + for (bool bHas = aIter.first(); bHas; bHas = aIter.next()) + { + if (!ScChangeActionContent::GetContentCellType(aIter.getRefCellValue())) + continue; + + aBigPos.Set( aIter.GetPos().Col() + nDx, aIter.GetPos().Row() + nDy, + aIter.GetPos().Tab() + nDz ); + ScChangeActionContent* pContent = SearchContentAt( aBigPos, nullptr ); + if (pContent) + continue; + + // Untracked Contents + aPos.Set( aIter.GetPos().Col() + nDx, aIter.GetPos().Row() + nDy, + aIter.GetPos().Tab() + nDz ); + + GenerateDelContent(aPos, aIter.getCellValue(), pRefDoc); + // The Content is _not_ added with AddContent here, but in UpdateReference. + // We do this in order to e.g. handle intersecting Deletes correctly + } +} + +void ScChangeTrack::AppendMove( const ScRange& rFromRange, + const ScRange& rToRange, ScDocument* pRefDoc ) +{ + ScChangeActionMove* pAct = new ScChangeActionMove( rFromRange, rToRange, this ); + LookUpContents( rToRange, pRefDoc, 0, 0, 0 ); // Overwritten Contents + Append( pAct ); +} + +bool ScChangeTrack::IsMatrixFormulaRangeDifferent( + const ScCellValue& rOldCell, const ScCellValue& rNewCell ) +{ + SCCOL nC1, nC2; + SCROW nR1, nR2; + nC1 = nC2 = 0; + nR1 = nR2 = 0; + + if (rOldCell.getType() == CELLTYPE_FORMULA && rOldCell.getFormula()->GetMatrixFlag() == ScMatrixMode::Formula) + rOldCell.getFormula()->GetMatColsRows(nC1, nR1); + + if (rNewCell.getType() == CELLTYPE_FORMULA && rNewCell.getFormula()->GetMatrixFlag() == ScMatrixMode::Formula) + rNewCell.getFormula()->GetMatColsRows(nC1, nR1); + + return nC1 != nC2 || nR1 != nR2; +} + +void ScChangeTrack::AppendContent( + const ScAddress& rPos, const ScCellValue& rOldCell, sal_uLong nOldFormat, ScDocument* pRefDoc ) +{ + if ( !pRefDoc ) + pRefDoc = &rDoc; + + OUString aOldValue = ScChangeActionContent::GetStringOfCell(rOldCell, pRefDoc, nOldFormat); + + ScCellValue aNewCell; + aNewCell.assign(rDoc, rPos); + OUString aNewValue = ScChangeActionContent::GetStringOfCell(aNewCell, &rDoc, rPos); + + if (aOldValue != aNewValue || IsMatrixFormulaRangeDifferent(rOldCell, aNewCell)) + { // Only track real changes + ScRange aRange( rPos ); + ScChangeActionContent* pAct = new ScChangeActionContent( aRange ); + pAct->SetOldValue(rOldCell, pRefDoc, &rDoc, nOldFormat); + pAct->SetNewValue(aNewCell, &rDoc); + Append( pAct ); + } +} + +void ScChangeTrack::AppendContent( const ScAddress& rPos, + const ScDocument* pRefDoc ) +{ + ScCellValue aOldCell; + aOldCell.assign(*pRefDoc, rPos); + OUString aOldValue = ScChangeActionContent::GetStringOfCell(aOldCell, pRefDoc, rPos); + + ScCellValue aNewCell; + aNewCell.assign(rDoc, rPos); + OUString aNewValue = ScChangeActionContent::GetStringOfCell(aNewCell, &rDoc, rPos); + + if (aOldValue != aNewValue || IsMatrixFormulaRangeDifferent(aOldCell, aNewCell)) + { // Only track real changes + ScRange aRange( rPos ); + ScChangeActionContent* pAct = new ScChangeActionContent( aRange ); + pAct->SetOldValue(aOldCell, pRefDoc, &rDoc); + pAct->SetNewValue(aNewCell, &rDoc); + Append( pAct ); + } +} + +void ScChangeTrack::AppendContent( const ScAddress& rPos, const ScCellValue& rOldCell ) +{ + if (ScChangeActionContent::NeedsNumberFormat(rOldCell)) + AppendContent(rPos, rOldCell, rDoc.GetNumberFormat(rPos), &rDoc); + else + AppendContent(rPos, rOldCell, 0, &rDoc); +} + +void ScChangeTrack::SetLastCutMoveRange( const ScRange& rRange, + ScDocument* pRefDoc ) +{ + if ( !pLastCutMove ) + return; + + // Do not link ToRange with Deletes and don't change its size + // This is actually unnecessary, as a delete triggers a ResetLastCut + // in ScViewFunc::PasteFromClip before that + ScBigRange& r = pLastCutMove->GetBigRange(); + r.aEnd.SetCol( -1 ); + r.aEnd.SetRow( -1 ); + r.aEnd.SetTab( -1 ); + r.aStart.SetCol( -1 - (rRange.aEnd.Col() - rRange.aStart.Col()) ); + r.aStart.SetRow( -1 - (rRange.aEnd.Row() - rRange.aStart.Row()) ); + r.aStart.SetTab( -1 - (rRange.aEnd.Tab() - rRange.aStart.Tab()) ); + // Contents in FromRange we should overwrite + LookUpContents( rRange, pRefDoc, 0, 0, 0 ); +} + +void ScChangeTrack::AppendContentRange( const ScRange& rRange, + ScDocument* pRefDoc, sal_uLong& nStartAction, sal_uLong& nEndAction, + ScChangeActionClipMode eClipMode ) +{ + if ( eClipMode == SC_CACM_CUT ) + { + ResetLastCut(); + pLastCutMove.reset(new ScChangeActionMove( rRange, rRange, this )); + SetLastCutMoveRange( rRange, pRefDoc ); + } + SCCOL nCol1; + SCROW nRow1; + SCTAB nTab1; + SCCOL nCol2; + SCROW nRow2; + SCTAB nTab2; + rRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + bool bDoContents; + if ( eClipMode == SC_CACM_PASTE && HasLastCut() ) + { + bDoContents = false; + SetInPasteCut( true ); + // Adjust Paste and Cut; Paste can be larger a Range + ScRange aRange( rRange ); + ScBigRange& r = pLastCutMove->GetBigRange(); + SCCOL nTmpCol; + if ( (nTmpCol = static_cast<SCCOL>(r.aEnd.Col() - r.aStart.Col())) != (nCol2 - nCol1) ) + { + aRange.aEnd.SetCol( aRange.aStart.Col() + nTmpCol ); + nCol1 += nTmpCol + 1; + bDoContents = true; + } + SCROW nTmpRow; + if ( (nTmpRow = static_cast<SCROW>(r.aEnd.Row() - r.aStart.Row())) != (nRow2 - nRow1) ) + { + aRange.aEnd.SetRow( aRange.aStart.Row() + nTmpRow ); + nRow1 += nTmpRow + 1; + bDoContents = true; + } + SCTAB nTmpTab; + if ( (nTmpTab = static_cast<SCTAB>(r.aEnd.Tab() - r.aStart.Tab())) != (nTab2 - nTab1) ) + { + aRange.aEnd.SetTab( aRange.aStart.Tab() + nTmpTab ); + nTab1 += nTmpTab + 1; + bDoContents = true; + } + r = aRange; + Undo( nStartLastCut, nEndLastCut ); // Remember Cuts here + // StartAction only after Undo! + nStartAction = GetActionMax() + 1; + StartBlockModify( ScChangeTrackMsgType::Append, nStartAction ); + // Contents to overwrite in ToRange + LookUpContents( aRange, pRefDoc, 0, 0, 0 ); + pLastCutMove->SetStartLastCut( nStartLastCut ); + pLastCutMove->SetEndLastCut( nEndLastCut ); + Append( pLastCutMove.release() ); + ResetLastCut(); + SetInPasteCut( false ); + } + else + { + bDoContents = true; + nStartAction = GetActionMax() + 1; + StartBlockModify( ScChangeTrackMsgType::Append, nStartAction ); + } + if ( bDoContents ) + { + ScAddress aPos; + for ( SCTAB nTab = nTab1; nTab <= nTab2; nTab++ ) + { + aPos.SetTab( nTab ); + // AppendContent() is a no-op if both cells are empty. + SCCOL lastCol = std::max( pRefDoc->ClampToAllocatedColumns( nTab, nCol2 ), + rDoc.ClampToAllocatedColumns( nTab, nCol2 )); + for ( SCCOL nCol = nCol1; nCol <= lastCol; nCol++ ) + { + aPos.SetCol( nCol ); + SCROW lastRow = std::max( pRefDoc->GetLastDataRow( nTab, nCol, nCol, nRow2 ), + rDoc.GetLastDataRow( nTab, nCol, nCol, nRow2 )); + for ( SCROW nRow = nRow1; nRow <= lastRow; nRow++ ) + { + aPos.SetRow( nRow ); + AppendContent( aPos, pRefDoc ); + } + } + } + } + nEndAction = GetActionMax(); + EndBlockModify( nEndAction ); + if ( eClipMode == SC_CACM_CUT ) + { + nStartLastCut = nStartAction; + nEndLastCut = nEndAction; + } +} + +void ScChangeTrack::AppendContentsIfInRefDoc( ScDocument& rRefDoc, + sal_uLong& nStartAction, sal_uLong& nEndAction ) +{ + ScCellIterator aIter(rRefDoc, ScRange(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB)); + if (aIter.first()) + { + nStartAction = GetActionMax() + 1; + StartBlockModify( ScChangeTrackMsgType::Append, nStartAction ); + SvNumberFormatter* pFormatter = rRefDoc.GetFormatTable(); + do + { + const ScAddress& rPos = aIter.GetPos(); + const ScPatternAttr* pPat = rRefDoc.GetPattern(rPos); + AppendContent( + rPos, aIter.getCellValue(), pPat->GetNumberFormat(pFormatter), &rRefDoc); + } + while (aIter.next()); + + nEndAction = GetActionMax(); + EndBlockModify( nEndAction ); + } + else + nStartAction = nEndAction = 0; +} + +ScChangeActionContent* ScChangeTrack::AppendContentOnTheFly( + const ScAddress& rPos, const ScCellValue& rOldCell, const ScCellValue& rNewCell, + sal_uLong nOldFormat, sal_uLong nNewFormat ) +{ + ScRange aRange( rPos ); + ScChangeActionContent* pAct = new ScChangeActionContent( aRange ); + pAct->SetOldNewCells(rOldCell, nOldFormat, rNewCell, nNewFormat, &rDoc); + Append( pAct ); + return pAct; +} + +void ScChangeTrack::AppendInsert( const ScRange& rRange, bool bEndOfList ) +{ + ScChangeActionIns* pAct = new ScChangeActionIns(&rDoc, rRange, bEndOfList); + Append( pAct ); +} + +void ScChangeTrack::DeleteCellEntries( std::vector<ScChangeActionContent*>& rCellList, + const ScChangeAction* pDeletor ) +{ + for (ScChangeActionContent* pContent : rCellList) + { + pContent->RemoveDeletedIn( pDeletor ); + if ( IsGenerated( pContent->GetActionNumber() ) && + !pContent->IsDeletedIn() ) + DeleteGeneratedDelContent( pContent ); + } + rCellList.clear(); +} + +ScChangeActionContent* ScChangeTrack::GenerateDelContent( + const ScAddress& rPos, const ScCellValue& rCell, const ScDocument* pFromDoc ) +{ + ScChangeActionContent* pContent = new ScChangeActionContent( + ScRange( rPos ) ); + pContent->SetActionNumber( --nGeneratedMin ); + // Only NewValue + ScChangeActionContent::SetValue( pContent->maNewValue, pContent->maNewCell, + rPos, rCell, pFromDoc, &rDoc ); + // pNextContent and pPrevContent are not set + if ( pFirstGeneratedDelContent ) + { // Insert at front + pFirstGeneratedDelContent->pPrev = pContent; + pContent->pNext = pFirstGeneratedDelContent; + } + pFirstGeneratedDelContent = pContent; + aGeneratedMap.insert( std::make_pair( nGeneratedMin, pContent ) ); + NotifyModified( ScChangeTrackMsgType::Append, nGeneratedMin, nGeneratedMin ); + return pContent; +} + +void ScChangeTrack::DeleteGeneratedDelContent( ScChangeActionContent* pContent ) +{ + sal_uLong nAct = pContent->GetActionNumber(); + aGeneratedMap.erase( nAct ); + if ( pFirstGeneratedDelContent == pContent ) + pFirstGeneratedDelContent = static_cast<ScChangeActionContent*>(pContent->pNext); + if ( pContent->pNext ) + pContent->pNext->pPrev = pContent->pPrev; + if ( pContent->pPrev ) + pContent->pPrev->pNext = pContent->pNext; + delete pContent; + NotifyModified( ScChangeTrackMsgType::Remove, nAct, nAct ); + if ( nAct == nGeneratedMin ) + ++nGeneratedMin; // Only after NotifyModified due to IsGenerated! +} + +ScChangeActionContent* ScChangeTrack::SearchContentAt( + const ScBigAddress& rPos, const ScChangeAction* pButNotThis ) const +{ + SCSIZE nSlot = ComputeContentSlot( rPos.Row() ); + for ( ScChangeActionContent* p = ppContentSlots[nSlot]; p; + p = p->GetNextInSlot() ) + { + if ( p != pButNotThis && !p->IsDeletedIn() && + p->GetBigRange().aStart == rPos ) + { + ScChangeActionContent* pContent = p->GetTopContent(); + if ( !pContent->IsDeletedIn() ) + return pContent; + } + } + return nullptr; +} + +void ScChangeTrack::AddDependentWithNotify( ScChangeAction* pParent, + ScChangeAction* pDependent ) +{ + ScChangeActionLinkEntry* pLink = pParent->AddDependent( pDependent ); + pDependent->AddLink( pParent, pLink ); + if ( aModifiedLink.IsSet() ) + { + sal_uLong nMod = pParent->GetActionNumber(); + NotifyModified( ScChangeTrackMsgType::Parent, nMod, nMod ); + } +} + +void ScChangeTrack::Dependencies( ScChangeAction* pAct ) +{ + // Find the last dependency for Col/Row/Tab each + // Concatenate Content at the same position + // Move dependencies + ScChangeActionType eActType = pAct->GetType(); + if ( eActType == SC_CAT_REJECT || + (eActType == SC_CAT_MOVE && pAct->IsRejecting()) ) + return ; // These Rejects are not dependent + + if ( eActType == SC_CAT_CONTENT ) + { + if ( !(static_cast<ScChangeActionContent*>(pAct)->GetNextContent() || + static_cast<ScChangeActionContent*>(pAct)->GetPrevContent()) ) + { // Concatenate Contents at same position + ScChangeActionContent* pContent = SearchContentAt( + pAct->GetBigRange().aStart, pAct ); + if ( pContent ) + { + pContent->SetNextContent( static_cast<ScChangeActionContent*>(pAct) ); + static_cast<ScChangeActionContent*>(pAct)->SetPrevContent( pContent ); + } + } + const ScCellValue& rCell = static_cast<ScChangeActionContent*>(pAct)->GetNewCell(); + if ( ScChangeActionContent::GetContentCellType(rCell) == SC_CACCT_MATREF ) + { + ScAddress aOrg; + bool bOrgFound = rCell.getFormula()->GetMatrixOrigin(rDoc, aOrg); + ScChangeActionContent* pContent = (bOrgFound ? SearchContentAt( aOrg, pAct ) : nullptr); + if ( pContent && pContent->IsMatrixOrigin() ) + { + AddDependentWithNotify( pContent, pAct ); + } + else + { + OSL_FAIL( "ScChangeTrack::Dependencies: MatOrg not found" ); + } + } + } + + if ( !(pLinkInsertCol || pLinkInsertRow || pLinkInsertTab || pLinkMove) ) + return ; // No Dependencies + if ( pAct->IsRejecting() ) + return ; // Except for Content no Dependencies + + // Insert in a corresponding Insert depends on it or else we would need + // to split the preceding one. + // Intersecting Inserts and Deletes are not dependent, everything else + // is dependent. + // The Insert last linked in is at the beginning of a chain, just the way we need it + + const ScBigRange& rRange = pAct->GetBigRange(); + bool bActNoInsert = !pAct->IsInsertType(); + bool bActColDel = ( eActType == SC_CAT_DELETE_COLS ); + bool bActRowDel = ( eActType == SC_CAT_DELETE_ROWS ); + bool bActTabDel = ( eActType == SC_CAT_DELETE_TABS ); + + if ( pLinkInsertCol && (eActType == SC_CAT_INSERT_COLS || + (bActNoInsert && !bActRowDel && !bActTabDel)) ) + { + for ( ScChangeActionLinkEntry* pL = pLinkInsertCol; pL; pL = pL->GetNext() ) + { + ScChangeActionIns* pTest = static_cast<ScChangeActionIns*>(pL->GetAction()); + if ( !pTest->IsRejected() && + pTest->GetBigRange().Intersects( rRange ) ) + { + AddDependentWithNotify( pTest, pAct ); + break; // for + } + } + } + if ( pLinkInsertRow && (eActType == SC_CAT_INSERT_ROWS || + (bActNoInsert && !bActColDel && !bActTabDel)) ) + { + for ( ScChangeActionLinkEntry* pL = pLinkInsertRow; pL; pL = pL->GetNext() ) + { + ScChangeActionIns* pTest = static_cast<ScChangeActionIns*>(pL->GetAction()); + if ( !pTest->IsRejected() && + pTest->GetBigRange().Intersects( rRange ) ) + { + AddDependentWithNotify( pTest, pAct ); + break; // for + } + } + } + if ( pLinkInsertTab && (eActType == SC_CAT_INSERT_TABS || + (bActNoInsert && !bActColDel && !bActRowDel)) ) + { + for ( ScChangeActionLinkEntry* pL = pLinkInsertTab; pL; pL = pL->GetNext() ) + { + ScChangeActionIns* pTest = static_cast<ScChangeActionIns*>(pL->GetAction()); + if ( !pTest->IsRejected() && + pTest->GetBigRange().Intersects( rRange ) ) + { + AddDependentWithNotify( pTest, pAct ); + break; // for + } + } + } + + if ( !pLinkMove ) + return; + + if ( eActType == SC_CAT_CONTENT ) + { // Content is depending on FromRange + const ScBigAddress& rPos = rRange.aStart; + for ( ScChangeActionLinkEntry* pL = pLinkMove; pL; pL = pL->GetNext() ) + { + ScChangeActionMove* pTest = static_cast<ScChangeActionMove*>(pL->GetAction()); + if ( !pTest->IsRejected() && + pTest->GetFromRange().Contains( rPos ) ) + { + AddDependentWithNotify( pTest, pAct ); + } + } + } + else if ( eActType == SC_CAT_MOVE ) + { // Move FromRange is depending on ToRange + const ScBigRange& rFromRange = static_cast<ScChangeActionMove*>(pAct)->GetFromRange(); + for ( ScChangeActionLinkEntry* pL = pLinkMove; pL; pL = pL->GetNext() ) + { + ScChangeActionMove* pTest = static_cast<ScChangeActionMove*>(pL->GetAction()); + if ( !pTest->IsRejected() && + pTest->GetBigRange().Intersects( rFromRange ) ) + { + AddDependentWithNotify( pTest, pAct ); + } + } + } + else + { // Inserts and Deletes are depending as soon as they cross FromRange or + // ToRange + for ( ScChangeActionLinkEntry* pL = pLinkMove; pL; pL = pL->GetNext() ) + { + ScChangeActionMove* pTest = static_cast<ScChangeActionMove*>(pL->GetAction()); + if ( !pTest->IsRejected() && + (pTest->GetFromRange().Intersects( rRange ) || + pTest->GetBigRange().Intersects( rRange )) ) + { + AddDependentWithNotify( pTest, pAct ); + } + } + } +} + +void ScChangeTrack::Remove( ScChangeAction* pRemove ) +{ + // Remove from Track + sal_uLong nAct = pRemove->GetActionNumber(); + aMap.erase( nAct ); + if ( nAct == nActionMax ) + --nActionMax; + if ( pRemove == pLast ) + pLast = pRemove->pPrev; + if ( pRemove == pFirst ) + pFirst = pRemove->pNext; + if ( nAct == nMarkLastSaved ) + nMarkLastSaved = + ( pRemove->pPrev ? pRemove->pPrev->GetActionNumber() : 0 ); + + // Remove from global chain + if ( pRemove->pNext ) + pRemove->pNext->pPrev = pRemove->pPrev; + if ( pRemove->pPrev ) + pRemove->pPrev->pNext = pRemove->pNext; + + // Don't delete Dependencies + // That happens automatically on delete by LinkEntry without traversing lists + if ( aModifiedLink.IsSet() ) + { + NotifyModified( ScChangeTrackMsgType::Remove, nAct, nAct ); + if ( pRemove->GetType() == SC_CAT_CONTENT ) + { + ScChangeActionContent* pContent = static_cast<ScChangeActionContent*>(pRemove); + if ( ( pContent = pContent->GetPrevContent() ) != nullptr ) + { + sal_uLong nMod = pContent->GetActionNumber(); + NotifyModified( ScChangeTrackMsgType::Change, nMod, nMod ); + } + } + else if ( pLast ) + NotifyModified( ScChangeTrackMsgType::Change, pFirst->GetActionNumber(), + pLast->GetActionNumber() ); + } + + if ( IsInPasteCut() && pRemove->GetType() == SC_CAT_CONTENT ) + { // Content is reused! + ScChangeActionContent* pContent = static_cast<ScChangeActionContent*>(pRemove); + pContent->RemoveAllLinks(); + pContent->ClearTrack(); + pContent->pNext = pContent->pPrev = nullptr; + pContent->pNextContent = pContent->pPrevContent = nullptr; + } +} + +void ScChangeTrack::Undo( sal_uLong nStartAction, sal_uLong nEndAction, bool bMerge ) +{ + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + if ( bMerge ) + { + SetMergeState( SC_CTMS_UNDO ); + } + + if ( nStartAction == 0 ) + ++nStartAction; + if ( nEndAction > nActionMax ) + nEndAction = nActionMax; + if ( nEndAction && nStartAction <= nEndAction ) + { + if ( nStartAction == nStartLastCut && nEndAction == nEndLastCut && + !IsInPasteCut() ) + ResetLastCut(); + StartBlockModify( ScChangeTrackMsgType::Remove, nStartAction ); + for ( sal_uLong j = nEndAction; j >= nStartAction; --j ) + { // Traverse backwards to recycle nActionMax and for faster access via pLast + // Deletes are in right order + ScChangeAction* pAct = IsLastAction(j) ? pLast : GetAction(j); + + if (!pAct) + continue; + + if ( pAct->IsDeleteType() ) + { + if (j == nEndAction || (pAct != pLast && static_cast<ScChangeActionDel*>(pAct)->IsTopDelete())) + { + SetInDeleteTop( true ); + SetInDeleteRange( static_cast<ScChangeActionDel*>(pAct)->GetOverAllRange().MakeRange( rDoc ) ); + } + } + UpdateReference( pAct, true ); + SetInDeleteTop( false ); + Remove( pAct ); + if ( IsInPasteCut() ) + { + aPasteCutMap.insert( ::std::make_pair( pAct->GetActionNumber(), pAct ) ); + continue; + } + + if ( j == nStartAction && pAct->GetType() == SC_CAT_MOVE ) + { + ScChangeActionMove* pMove = static_cast<ScChangeActionMove*>(pAct); + sal_uLong nStart = pMove->GetStartLastCut(); + sal_uLong nEnd = pMove->GetEndLastCut(); + if ( nStart && nStart <= nEnd ) + { // Recover LastCut + // Break Links before Cut Append! + pMove->RemoveAllLinks(); + StartBlockModify( ScChangeTrackMsgType::Append, nStart ); + for ( sal_uLong nCut = nStart; nCut <= nEnd; nCut++ ) + { + ScChangeActionMap::iterator itCut = aPasteCutMap.find( nCut ); + + if ( itCut != aPasteCutMap.end() ) + { + OSL_ENSURE( aMap.find( nCut ) == aMap.end(), "ScChangeTrack::Undo: nCut dup" ); + Append( itCut->second, nCut ); + aPasteCutMap.erase( itCut ); + } + else + { + OSL_FAIL( "ScChangeTrack::Undo: nCut not found" ); + } + } + EndBlockModify( nEnd ); + ResetLastCut(); + nStartLastCut = nStart; + nEndLastCut = nEnd; + pLastCutMove.reset(pMove); + SetLastCutMoveRange( + pMove->GetFromRange().MakeRange( rDoc ), &rDoc ); + } + else + delete pMove; + } + else + delete pAct; + } + EndBlockModify( nEndAction ); + } + + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + if ( bMerge ) + { + SetMergeState( SC_CTMS_OTHER ); + } +} + +bool ScChangeTrack::MergeIgnore( const ScChangeAction& rAction, sal_uLong nFirstMerge ) +{ + if ( rAction.IsRejected() ) + return true; // There's still a suitable Reject Action coming + + if ( rAction.IsRejecting() && rAction.GetRejectAction() >= nFirstMerge ) + return true; // There it is + + return false; // Everything else +} + +void ScChangeTrack::MergePrepare( const ScChangeAction* pFirstMerge, bool bShared ) +{ + SetMergeState( SC_CTMS_PREPARE ); + sal_uLong nFirstMerge = pFirstMerge->GetActionNumber(); + ScChangeAction* pAct = GetLast(); + if ( pAct ) + { + SetLastMerge( pAct->GetActionNumber() ); + while ( pAct ) + { // Traverse backwards; Deletes in right order + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + if ( bShared || !ScChangeTrack::MergeIgnore( *pAct, nFirstMerge ) ) + { + if ( pAct->IsDeleteType() ) + { + if ( static_cast<ScChangeActionDel*>(pAct)->IsTopDelete() ) + { + SetInDeleteTop( true ); + SetInDeleteRange( static_cast<ScChangeActionDel*>(pAct)-> + GetOverAllRange().MakeRange( rDoc ) ); + } + } + UpdateReference( pAct, true ); + SetInDeleteTop( false ); + pAct->DeleteCellEntries(); // Else segfault in Track Clear() + } + pAct = ( pAct == pFirstMerge ? nullptr : pAct->GetPrev() ); + } + } + SetMergeState( SC_CTMS_OTHER ); // Preceding by default MergeOther! +} + +void ScChangeTrack::MergeOwn( ScChangeAction* pAct, sal_uLong nFirstMerge, bool bShared ) +{ + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + if ( !bShared && ScChangeTrack::MergeIgnore( *pAct, nFirstMerge ) ) + return; + + SetMergeState( SC_CTMS_OWN ); + if ( pAct->IsDeleteType() ) + { + if ( static_cast<ScChangeActionDel*>(pAct)->IsTopDelete() ) + { + SetInDeleteTop( true ); + SetInDeleteRange( static_cast<ScChangeActionDel*>(pAct)-> + GetOverAllRange().MakeRange( rDoc ) ); + } + } + UpdateReference( pAct, false ); + SetInDeleteTop( false ); + SetMergeState( SC_CTMS_OTHER ); // Preceding by default MergeOther! +} + +void ScChangeTrack::UpdateReference( ScChangeAction* pAct, bool bUndo ) +{ + ScChangeActionType eActType = pAct->GetType(); + if ( eActType == SC_CAT_CONTENT || eActType == SC_CAT_REJECT ) + return ; + + // Formula cells are not in the Document! + bool bOldAutoCalc = rDoc.GetAutoCalc(); + rDoc.SetAutoCalc( false ); + bool bOldNoListening = rDoc.GetNoListening(); + rDoc.SetNoListening( true ); + + // Formula cells ExpandRefs synchronized to the ones in the Document! + bool bOldExpandRefs = rDoc.IsExpandRefs(); + if ( (!bUndo && pAct->IsInsertType()) || (bUndo && pAct->IsDeleteType()) ) + rDoc.SetExpandRefs( SC_MOD()->GetInputOptions().GetExpandRefs() ); + + if ( pAct->IsDeleteType() ) + { + SetInDeleteUndo( bUndo ); + SetInDelete( true ); + } + else if ( GetMergeState() == SC_CTMS_OWN ) + { + // Recover references of formula cells + // Previous MergePrepare behaved like a Delete when Inserting + if ( pAct->IsInsertType() ) + SetInDeleteUndo( true ); + } + + // First the generated ones, as if they were tracked previously! + if ( pFirstGeneratedDelContent ) + UpdateReference( reinterpret_cast<ScChangeAction**>(&pFirstGeneratedDelContent), pAct, + bUndo ); + UpdateReference( &pFirst, pAct, bUndo ); + + SetInDelete( false ); + SetInDeleteUndo( false ); + + rDoc.SetExpandRefs( bOldExpandRefs ); + rDoc.SetNoListening( bOldNoListening ); + rDoc.SetAutoCalc( bOldAutoCalc ); +} + +void ScChangeTrack::UpdateReference( ScChangeAction** ppFirstAction, + ScChangeAction* pAct, bool bUndo ) +{ + ScChangeActionType eActType = pAct->GetType(); + bool bGeneratedDelContents = + ( ppFirstAction == reinterpret_cast<ScChangeAction**>(&pFirstGeneratedDelContent) ); + const ScBigRange& rOrgRange = pAct->GetBigRange(); + ScBigRange aRange( rOrgRange ); + ScBigRange aDelRange( rOrgRange ); + sal_Int32 nDx, nDy, nDz; + nDx = nDy = nDz = 0; + UpdateRefMode eMode = URM_INSDEL; + bool bDel = false; + switch ( eActType ) + { + case SC_CAT_INSERT_COLS : + aRange.aEnd.SetCol( ScBigRange::nRangeMax ); + nDx = rOrgRange.aEnd.Col() - rOrgRange.aStart.Col() + 1; + break; + case SC_CAT_INSERT_ROWS : + aRange.aEnd.SetRow( ScBigRange::nRangeMax ); + nDy = rOrgRange.aEnd.Row() - rOrgRange.aStart.Row() + 1; + break; + case SC_CAT_INSERT_TABS : + aRange.aEnd.SetTab( ScBigRange::nRangeMax ); + nDz = rOrgRange.aEnd.Tab() - rOrgRange.aStart.Tab() + 1; + break; + case SC_CAT_DELETE_COLS : + aRange.aEnd.SetCol( ScBigRange::nRangeMax ); + nDx = -(rOrgRange.aEnd.Col() - rOrgRange.aStart.Col() + 1); + aDelRange.aEnd.SetCol( aDelRange.aStart.Col() - nDx - 1 ); + bDel = true; + break; + case SC_CAT_DELETE_ROWS : + aRange.aEnd.SetRow( ScBigRange::nRangeMax ); + nDy = -(rOrgRange.aEnd.Row() - rOrgRange.aStart.Row() + 1); + aDelRange.aEnd.SetRow( aDelRange.aStart.Row() - nDy - 1 ); + bDel = true; + break; + case SC_CAT_DELETE_TABS : + aRange.aEnd.SetTab( ScBigRange::nRangeMax ); + nDz = -(rOrgRange.aEnd.Tab() - rOrgRange.aStart.Tab() + 1); + aDelRange.aEnd.SetTab( aDelRange.aStart.Tab() - nDz - 1 ); + bDel = true; + break; + case SC_CAT_MOVE : + eMode = URM_MOVE; + static_cast<ScChangeActionMove*>(pAct)->GetDelta( nDx, nDy, nDz ); + break; + default: + OSL_FAIL( "ScChangeTrack::UpdateReference: unknown Type" ); + } + if ( bUndo ) + { + nDx = -nDx; + nDy = -nDy; + nDz = -nDz; + } + if ( bDel ) + { // For this mechanism we assume: + // There's only a whole, simple deleted row/column + ScChangeActionDel* pActDel = static_cast<ScChangeActionDel*>(pAct); + if ( !bUndo ) + { // Delete + ScChangeActionType eInsType = SC_CAT_NONE; // for Insert Undo "Deletes" + switch ( eActType ) + { + case SC_CAT_DELETE_COLS : + eInsType = SC_CAT_INSERT_COLS; + break; + case SC_CAT_DELETE_ROWS : + eInsType = SC_CAT_INSERT_ROWS; + break; + case SC_CAT_DELETE_TABS : + eInsType = SC_CAT_INSERT_TABS; + break; + default: + { + // added to avoid warnings + } + } + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p == pAct ) + continue; // for + bool bUpdate = true; + if ( GetMergeState() == SC_CTMS_OTHER && + p->GetActionNumber() <= GetLastMerge() ) + { // Delete in merged Document, Action in the one to be merged + if ( p->IsInsertType() ) + { + // On Insert only adjust references if the Delete does + // not intersect the Insert + if ( !aDelRange.Intersects( p->GetBigRange() ) ) + p->UpdateReference( this, eMode, aRange, nDx, nDy, nDz ); + bUpdate = false; + } + else if ( p->GetType() == SC_CAT_CONTENT && + p->IsDeletedInDelType( eInsType ) ) + { // Content in Insert Undo "Delete" + // Do not adjust if this Delete would be in the Insert "Delete" (was just moved) + if ( aDelRange.Contains( p->GetBigRange().aStart ) ) + bUpdate = false; + else + { + const ScChangeActionLinkEntry* pLink = p->GetDeletedIn(); + while ( pLink && bUpdate ) + { + const ScChangeAction* pDel = pLink->GetAction(); + if ( pDel && pDel->GetType() == eInsType && + pDel->GetBigRange().Contains( aDelRange ) ) + bUpdate = false; + pLink = pLink->GetNext(); + } + } + } + if ( !bUpdate ) + continue; // for + } + if ( aDelRange.Contains( p->GetBigRange() ) ) + { + // Do not adjust within a just deleted range, + // instead assign the range. + // Stack up ranges that have been deleted multiple times. + // Intersecting Deletes cause "multiple delete" to be set. + if ( !p->IsDeletedInDelType( eActType ) ) + { + p->SetDeletedIn( pActDel ); + // Add GeneratedDelContent to the to-be-deleted list + if ( bGeneratedDelContents ) + pActDel->AddContent( static_cast<ScChangeActionContent*>(p) ); + } + bUpdate = false; + } + else + { + // Cut off inserted ranges, if Start/End is within the Delete, + // but the Insert is not completely within the Delete or + // the Delete is not completely within the Insert. + // The Delete remembers which Insert it has cut off from; + // it can also just be a single Insert (because Delete has + // a single column/is a single row). + // There can be a lot of cut-off Moves. + // + // ! A Delete is always a single column/a single row, therefore + // ! 1 without calculating the intersection. + switch ( p->GetType() ) + { + case SC_CAT_INSERT_COLS : + if ( eActType == SC_CAT_DELETE_COLS ) + { + if ( aDelRange.Contains( p->GetBigRange().aStart ) ) + { + pActDel->SetCutOffInsert( + static_cast<ScChangeActionIns*>(p), 1 ); + p->GetBigRange().aStart.IncCol(); + } + else if ( aDelRange.Contains( p->GetBigRange().aEnd ) ) + { + pActDel->SetCutOffInsert( + static_cast<ScChangeActionIns*>(p), -1 ); + p->GetBigRange().aEnd.IncCol( -1 ); + } + } + break; + case SC_CAT_INSERT_ROWS : + if ( eActType == SC_CAT_DELETE_ROWS ) + { + if ( aDelRange.Contains( p->GetBigRange().aStart ) ) + { + pActDel->SetCutOffInsert( + static_cast<ScChangeActionIns*>(p), 1 ); + p->GetBigRange().aStart.IncRow(); + } + else if ( aDelRange.Contains( p->GetBigRange().aEnd ) ) + { + pActDel->SetCutOffInsert( + static_cast<ScChangeActionIns*>(p), -1 ); + p->GetBigRange().aEnd.IncRow( -1 ); + } + } + break; + case SC_CAT_INSERT_TABS : + if ( eActType == SC_CAT_DELETE_TABS ) + { + if ( aDelRange.Contains( p->GetBigRange().aStart ) ) + { + pActDel->SetCutOffInsert( + static_cast<ScChangeActionIns*>(p), 1 ); + p->GetBigRange().aStart.IncTab(); + } + else if ( aDelRange.Contains( p->GetBigRange().aEnd ) ) + { + pActDel->SetCutOffInsert( + static_cast<ScChangeActionIns*>(p), -1 ); + p->GetBigRange().aEnd.IncTab( -1 ); + } + } + break; + case SC_CAT_MOVE : + { + ScChangeActionMove* pMove = static_cast<ScChangeActionMove*>(p); + short nFrom = 0; + short nTo = 0; + if ( aDelRange.Contains( pMove->GetBigRange().aStart ) ) + nTo = 1; + else if ( aDelRange.Contains( pMove->GetBigRange().aEnd ) ) + nTo = -1; + if ( aDelRange.Contains( pMove->GetFromRange().aStart ) ) + nFrom = 1; + else if ( aDelRange.Contains( pMove->GetFromRange().aEnd ) ) + nFrom = -1; + if ( nFrom ) + { + switch ( eActType ) + { + case SC_CAT_DELETE_COLS : + if ( nFrom > 0 ) + pMove->GetFromRange().aStart.IncCol( nFrom ); + else + pMove->GetFromRange().aEnd.IncCol( nFrom ); + break; + case SC_CAT_DELETE_ROWS : + if ( nFrom > 0 ) + pMove->GetFromRange().aStart.IncRow( nFrom ); + else + pMove->GetFromRange().aEnd.IncRow( nFrom ); + break; + case SC_CAT_DELETE_TABS : + if ( nFrom > 0 ) + pMove->GetFromRange().aStart.IncTab( nFrom ); + else + pMove->GetFromRange().aEnd.IncTab( nFrom ); + break; + default: + { + // added to avoid warnings + } + } + } + if ( nTo ) + { + switch ( eActType ) + { + case SC_CAT_DELETE_COLS : + if ( nTo > 0 ) + pMove->GetBigRange().aStart.IncCol( nTo ); + else + pMove->GetBigRange().aEnd.IncCol( nTo ); + break; + case SC_CAT_DELETE_ROWS : + if ( nTo > 0 ) + pMove->GetBigRange().aStart.IncRow( nTo ); + else + pMove->GetBigRange().aEnd.IncRow( nTo ); + break; + case SC_CAT_DELETE_TABS : + if ( nTo > 0 ) + pMove->GetBigRange().aStart.IncTab( nTo ); + else + pMove->GetBigRange().aEnd.IncTab( nTo ); + break; + default: + { + // added to avoid warnings + } + } + } + if ( nFrom || nTo ) + { + ScChangeActionDelMoveEntry* pLink = + pActDel->AddCutOffMove( pMove, nFrom, nTo ); + pMove->AddLink( pActDel, pLink ); + } + } + break; + default: + { + // added to avoid warnings + } + } + } + if ( bUpdate ) + { + p->UpdateReference( this, eMode, aRange, nDx, nDy, nDz ); + if ( p->GetType() == eActType && !p->IsRejected() && + !pActDel->IsDeletedIn() && + p->GetBigRange().Contains( aDelRange ) ) + pActDel->SetDeletedIn( p ); // Slipped underneath it + } + } + } + else + { // Undo Delete + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p == pAct ) + continue; // for + bool bUpdate = true; + if ( aDelRange.Contains( p->GetBigRange() ) ) + { + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + if ( GetMergeState() == SC_CTMS_UNDO && !p->IsDeletedIn( pAct ) && pAct->IsDeleteType() && + ( p->GetType() == SC_CAT_CONTENT || + p->GetType() == SC_CAT_DELETE_ROWS || p->GetType() == SC_CAT_DELETE_COLS || + p->GetType() == SC_CAT_INSERT_ROWS || p->GetType() == SC_CAT_INSERT_COLS ) ) + { + p->SetDeletedIn( pAct ); + } + + if ( p->IsDeletedInDelType( eActType ) ) + { + if ( p->IsDeletedIn( pActDel ) ) + { + if ( p->GetType() != SC_CAT_CONTENT || + static_cast<ScChangeActionContent*>(p)->IsTopContent() ) + { // First really remove the TopContent + p->RemoveDeletedIn( pActDel ); + // Do NOT delete GeneratedDelContent from the list, we might need + // it later on for Reject; we delete in DeleteCellEntries + } + } + bUpdate = false; + } + else if ( eActType != SC_CAT_DELETE_TABS && + p->IsDeletedInDelType( SC_CAT_DELETE_TABS ) ) + { // Do not update in deleted Tables except for when moving Tables + bUpdate = false; + } + if ( p->GetType() == eActType && pActDel->IsDeletedIn( p ) ) + { + pActDel->RemoveDeletedIn( p );// Slipped underneath + bUpdate = true; + } + } + if ( bUpdate ) + p->UpdateReference( this, eMode, aRange, nDx, nDy, nDz ); + } + if ( !bGeneratedDelContents ) + { // These are else also needed for the real Undo + pActDel->UndoCutOffInsert(); + pActDel->UndoCutOffMoves(); + } + } + } + else if ( eActType == SC_CAT_MOVE ) + { + ScChangeActionMove* pActMove = static_cast<ScChangeActionMove*>(pAct); + bool bLastCutMove = ( pActMove == pLastCutMove.get() ); + const ScBigRange& rTo = pActMove->GetBigRange(); + const ScBigRange& rFrom = pActMove->GetFromRange(); + if ( !bUndo ) + { // Move + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p == pAct ) + continue; // for + if ( p->GetType() == SC_CAT_CONTENT ) + { + // Delete content in Target (Move Content to Source) + if ( rTo.Contains( p->GetBigRange() ) ) + { + if ( !p->IsDeletedIn( pActMove ) ) + { + p->SetDeletedIn( pActMove ); + // Add GeneratedDelContent to the to-be-deleted list + if ( bGeneratedDelContents ) + pActMove->AddContent( static_cast<ScChangeActionContent*>(p) ); + } + } + else if ( bLastCutMove && + p->GetActionNumber() > nEndLastCut && + rFrom.Contains( p->GetBigRange() ) ) + { // Paste Cut: insert new Content inserted after stays + // Split up the ContentChain + ScChangeActionContent *pHere, *pTmp; + pHere = static_cast<ScChangeActionContent*>(p); + for (;;) + { + pTmp = pHere->GetPrevContent(); + if (!pTmp || pTmp->GetActionNumber() <= nEndLastCut) + break; + pHere = pTmp; + } + if ( pTmp ) + { // Becomes TopContent of the Move + pTmp->SetNextContent( nullptr ); + pHere->SetPrevContent( nullptr ); + } + do + { // Recover dependency from FromRange + AddDependentWithNotify( pActMove, pHere ); + } while ( ( pHere = pHere->GetNextContent() ) != nullptr ); + } + // #i87003# [Collaboration] Move range and insert content in FromRange is not merged correctly + else if ( ( GetMergeState() != SC_CTMS_PREPARE && GetMergeState() != SC_CTMS_OWN ) || p->GetActionNumber() <= pAct->GetActionNumber() ) + p->UpdateReference( this, eMode, rFrom, nDx, nDy, nDz ); + } + } + } + else + { // Undo Move + bool bActRejected = pActMove->IsRejected(); + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p == pAct ) + continue; // for + if ( p->GetType() == SC_CAT_CONTENT ) + { + // Move Content into Target if not deleted else to delete (FIXME: What?) + if ( p->IsDeletedIn( pActMove ) ) + { + if ( static_cast<ScChangeActionContent*>(p)->IsTopContent() ) + { // First really remove the TopContent + p->RemoveDeletedIn( pActMove ); + // Do NOT delete GeneratedDelContent from the list, we might need + // it later on for Reject; we delete in DeleteCellEntries + } + } + // #i87003# [Collaboration] Move range and insert content in FromRange is not merged correctly + else if ( ( GetMergeState() != SC_CTMS_PREPARE && GetMergeState() != SC_CTMS_OWN ) || p->GetActionNumber() <= pAct->GetActionNumber() ) + p->UpdateReference( this, eMode, rTo, nDx, nDy, nDz ); + if ( bActRejected && + static_cast<ScChangeActionContent*>(p)->IsTopContent() && + rFrom.Contains( p->GetBigRange() ) ) + { // Recover dependency to write Content + ScChangeActionLinkEntry* pLink = + pActMove->AddDependent( p ); + p->AddLink( pActMove, pLink ); + } + } + } + } + } + else + { // Insert/Undo Insert + switch ( GetMergeState() ) + { + case SC_CTMS_NONE : + case SC_CTMS_OTHER : + { + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p == pAct ) + continue; // for + p->UpdateReference( this, eMode, aRange, nDx, nDy, nDz ); + } + } + break; + case SC_CTMS_PREPARE : + { + // "Delete" in Insert-Undo + const ScChangeActionLinkEntry* pLink = pAct->GetFirstDependentEntry(); + while ( pLink ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pLink->GetAction()); + if ( p ) + p->SetDeletedIn( pAct ); + pLink = pLink->GetNext(); + } + + // #i87049# [Collaboration] Conflict between delete row and insert content is not merged correctly + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( !p->IsDeletedIn( pAct ) && pAct->IsInsertType() && + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + ( p->GetType() == SC_CAT_CONTENT || + p->GetType() == SC_CAT_DELETE_ROWS || p->GetType() == SC_CAT_DELETE_COLS || + p->GetType() == SC_CAT_INSERT_ROWS || p->GetType() == SC_CAT_INSERT_COLS ) && + pAct->GetBigRange().Intersects( p->GetBigRange() ) ) + { + p->SetDeletedIn( pAct ); + } + } + + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p == pAct ) + continue; // for + if ( !p->IsDeletedIn( pAct ) + // #i95212# [Collaboration] Bad handling of row insertion in shared spreadsheet + && p->GetActionNumber() <= pAct->GetActionNumber() ) + { + p->UpdateReference( this, eMode, aRange, nDx, nDy, nDz ); + } + } + } + break; + case SC_CTMS_OWN : + { + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p == pAct ) + continue; // for + if ( !p->IsDeletedIn( pAct ) + // #i95212# [Collaboration] Bad handling of row insertion in shared spreadsheet + && p->GetActionNumber() <= pAct->GetActionNumber() ) + { + p->UpdateReference( this, eMode, aRange, nDx, nDy, nDz ); + } + } + // Undo "Delete" in Insert-Undo + const ScChangeActionLinkEntry* pLink = pAct->GetFirstDependentEntry(); + while ( pLink ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pLink->GetAction()); + if ( p ) + p->RemoveDeletedIn( pAct ); + pLink = pLink->GetNext(); + } + + // #i87049# [Collaboration] Conflict between delete row and insert content is not merged correctly + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p->IsDeletedIn( pAct ) && pAct->IsInsertType() && + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + ( p->GetType() == SC_CAT_CONTENT || + p->GetType() == SC_CAT_DELETE_ROWS || p->GetType() == SC_CAT_DELETE_COLS || + p->GetType() == SC_CAT_INSERT_ROWS || p->GetType() == SC_CAT_INSERT_COLS ) && + pAct->GetBigRange().Intersects( p->GetBigRange() ) ) + { + p->RemoveDeletedIn( pAct ); + } + } + } + break; + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + case SC_CTMS_UNDO : + { + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( !p->IsDeletedIn( pAct ) && pAct->IsInsertType() && + ( p->GetType() == SC_CAT_CONTENT || + p->GetType() == SC_CAT_DELETE_ROWS || p->GetType() == SC_CAT_DELETE_COLS || + p->GetType() == SC_CAT_INSERT_ROWS || p->GetType() == SC_CAT_INSERT_COLS ) && + pAct->GetBigRange().Intersects( p->GetBigRange() ) ) + { + p->SetDeletedIn( pAct ); + } + } + + for ( ScChangeAction* p = *ppFirstAction; p; p = p->GetNext() ) + { + if ( p == pAct ) + { + continue; + } + if ( !p->IsDeletedIn( pAct ) && p->GetActionNumber() <= pAct->GetActionNumber() ) + { + p->UpdateReference( this, eMode, aRange, nDx, nDy, nDz ); + } + } + } + break; + } + } +} + +void ScChangeTrack::GetDependents( ScChangeAction* pAct, + ScChangeActionMap& rMap, bool bListMasterDelete, bool bAllFlat ) const +{ + //TODO: bAllFlat==TRUE: called internally from Accept or Reject + //TODO: => Generated will not be added + bool bIsDelete = pAct->IsDeleteType(); + bool bIsMasterDelete = ( bListMasterDelete && pAct->IsMasterDelete() ); + + const ScChangeAction* pCur = nullptr; + ::std::stack<ScChangeAction*> cStack; + cStack.push(pAct); + + while ( !cStack.empty() ) + { + pCur = cStack.top(); + cStack.pop(); + + if ( pCur->IsInsertType() ) + { + const ScChangeActionLinkEntry* pL = pCur->GetFirstDependentEntry(); + while ( pL ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pL->GetAction()); + if ( p != pAct ) + { + if ( bAllFlat ) + { + sal_uLong n = p->GetActionNumber(); + if ( !IsGenerated( n ) && rMap.insert( ::std::make_pair( n, p ) ).second ) + if ( p->HasDependent() ) + cStack.push( p ); + } + else + { + if ( p->GetType() == SC_CAT_CONTENT ) + { + if ( static_cast<ScChangeActionContent*>(p)->IsTopContent() ) + rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ); + } + else + rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ); + } + } + pL = pL->GetNext(); + } + } + else if ( pCur->IsDeleteType() ) + { + if ( bIsDelete ) + { // Contents of deleted Ranges are only of interest on Delete + ScChangeActionDel* pDel = const_cast<ScChangeActionDel*>(static_cast<const ScChangeActionDel*>(pCur)); + if ( !bAllFlat && bIsMasterDelete && pCur == pAct ) + { + // Corresponding Deletes to this Delete to the same level, + // if this Delete is at the top of a Row + ScChangeActionType eType = pDel->GetType(); + ScChangeAction* p = pDel; + for (;;) + { + p = p->GetPrev(); + if (!p || p->GetType() != eType || + static_cast<ScChangeActionDel*>(p)->IsTopDelete() ) + break; + rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ); + } + // delete this in the map too + rMap.insert( ::std::make_pair( pAct->GetActionNumber(), pAct ) ); + } + else + { + const ScChangeActionLinkEntry* pL = pCur->GetFirstDeletedEntry(); + while ( pL ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pL->GetAction()); + if ( p != pAct ) + { + if ( bAllFlat ) + { + // Only a TopContent of a chain is in LinkDeleted + sal_uLong n = p->GetActionNumber(); + if ( !IsGenerated( n ) && rMap.insert( ::std::make_pair( n, p ) ).second ) + if ( p->HasDeleted() || + p->GetType() == SC_CAT_CONTENT ) + cStack.push( p ); + } + else + { + if ( p->IsDeleteType() ) + { // Further TopDeletes to same level: it's not rejectable + if ( static_cast<ScChangeActionDel*>(p)->IsTopDelete() ) + rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ); + } + else + rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ); + } + } + pL = pL->GetNext(); + } + } + } + } + else if ( pCur->GetType() == SC_CAT_MOVE ) + { + // Deleted Contents in ToRange + const ScChangeActionLinkEntry* pL = pCur->GetFirstDeletedEntry(); + while ( pL ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pL->GetAction()); + if ( p != pAct && rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ).second ) + { + // Only one TopContent of a chain is in LinkDeleted + if ( bAllFlat && (p->HasDeleted() || + p->GetType() == SC_CAT_CONTENT) ) + cStack.push( p ); + } + pL = pL->GetNext(); + } + // New Contents in FromRange or new FromRange in ToRange + // or Inserts/Deletes in FromRange/ToRange + pL = pCur->GetFirstDependentEntry(); + while ( pL ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pL->GetAction()); + if ( p != pAct ) + { + if ( bAllFlat ) + { + sal_uLong n = p->GetActionNumber(); + if ( !IsGenerated( n ) && rMap.insert( ::std::make_pair( n, p ) ).second ) + if ( p->HasDependent() || p->HasDeleted() ) + cStack.push( p ); + } + else + { + if ( p->GetType() == SC_CAT_CONTENT ) + { + if ( static_cast<ScChangeActionContent*>(p)->IsTopContent() ) + rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ); + } + else + rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ); + } + } + pL = pL->GetNext(); + } + } + else if ( pCur->GetType() == SC_CAT_CONTENT ) + { // All changes at same position + ScChangeActionContent* pContent = const_cast<ScChangeActionContent*>(static_cast<const ScChangeActionContent*>(pCur)); + // All preceding ones + while ( ( pContent = pContent->GetPrevContent() ) != nullptr ) + { + if ( !pContent->IsRejected() ) + rMap.insert( ::std::make_pair( pContent->GetActionNumber(), pContent ) ); + } + pContent = const_cast<ScChangeActionContent*>(static_cast<const ScChangeActionContent*>(pCur)); + // All succeeding ones + while ( ( pContent = pContent->GetNextContent() ) != nullptr ) + { + if ( !pContent->IsRejected() ) + rMap.insert( ::std::make_pair( pContent->GetActionNumber(), pContent ) ); + } + // all MatrixReferences of a MatrixOrigin + const ScChangeActionLinkEntry* pL = pCur->GetFirstDependentEntry(); + while ( pL ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pL->GetAction()); + if ( p != pAct ) + { + if ( bAllFlat ) + { + sal_uLong n = p->GetActionNumber(); + if ( !IsGenerated( n ) && rMap.insert( ::std::make_pair( n, p ) ).second ) + if ( p->HasDependent() ) + cStack.push( p ); + } + else + rMap.insert( ::std::make_pair( p->GetActionNumber(), p ) ); + } + pL = pL->GetNext(); + } + } + else if ( pCur->GetType() == SC_CAT_REJECT ) + { + if ( bAllFlat ) + { + ScChangeAction* p = GetAction( + static_cast<const ScChangeActionReject*>(pCur)->GetRejectAction() ); + if (p != pAct && rMap.find( p->GetActionNumber() ) == rMap.end()) + cStack.push( p ); + } + } + } +} + +bool ScChangeTrack::SelectContent( ScChangeAction* pAct, bool bOldest ) +{ + if ( pAct->GetType() != SC_CAT_CONTENT ) + return false; + + ScChangeActionContent* pContent = static_cast<ScChangeActionContent*>(pAct); + if ( bOldest ) + { + pContent = pContent->GetTopContent(); + for (;;) + { + ScChangeActionContent* pPrevContent = pContent->GetPrevContent(); + if ( !pPrevContent || !pPrevContent->IsVirgin() ) + break; + pContent = pPrevContent; + } + } + + if ( !pContent->IsClickable() ) + return false; + + ScBigRange aBigRange( pContent->GetBigRange() ); + const ScCellValue& rCell = (bOldest ? pContent->GetOldCell() : pContent->GetNewCell()); + if ( ScChangeActionContent::GetContentCellType(rCell) == SC_CACCT_MATORG ) + { + SCCOL nC; + SCROW nR; + rCell.getFormula()->GetMatColsRows(nC, nR); + aBigRange.aEnd.IncCol( nC-1 ); + aBigRange.aEnd.IncRow( nR-1 ); + } + + if ( !aBigRange.IsValid( rDoc ) ) + return false; + + ScRange aRange( aBigRange.MakeRange( rDoc ) ); + if ( !rDoc.IsBlockEditable( aRange.aStart.Tab(), aRange.aStart.Col(), + aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row() ) ) + return false; + + if ( pContent->HasDependent() ) + { + bool bOk = true; + ::std::stack<ScChangeActionContent*> aRejectActions; + const ScChangeActionLinkEntry* pL = pContent->GetFirstDependentEntry(); + while ( pL ) + { + ScChangeAction* p = const_cast<ScChangeAction*>(pL->GetAction()); + if ( p != pContent ) + { + if ( p->GetType() == SC_CAT_CONTENT ) + { + // we don't need no recursion here, do we? + bOk &= static_cast<ScChangeActionContent*>(p)->Select( rDoc, this, + bOldest, &aRejectActions ); + } + else + { + OSL_FAIL( "ScChangeTrack::SelectContent: content dependent no content" ); + } + } + pL = pL->GetNext(); + } + + bOk &= pContent->Select( rDoc, this, bOldest, nullptr ); + // now the matrix is inserted and new content values are ready + + while ( !aRejectActions.empty() ) + { + ScChangeActionContent* pNew = aRejectActions.top(); + aRejectActions.pop(); + ScAddress aPos( pNew->GetBigRange().aStart.MakeAddress( rDoc ) ); + ScCellValue aCell; + aCell.assign(rDoc, aPos); + pNew->SetNewValue(aCell, &rDoc); + Append( pNew ); + } + return bOk; + } + else + return pContent->Select( rDoc, this, bOldest, nullptr ); +} + +void ScChangeTrack::AcceptAll() +{ + for ( ScChangeAction* p = GetFirst(); p; p = p->GetNext() ) + { + p->Accept(); + } +} + +bool ScChangeTrack::Accept( ScChangeAction* pAct ) +{ + if ( !pAct->IsClickable() ) + return false; + + if ( pAct->IsDeleteType() || pAct->GetType() == SC_CAT_CONTENT ) + { + ScChangeActionMap aActionMap; + + GetDependents( pAct, aActionMap, false, true ); + + for( auto& rEntry : aActionMap ) + { + rEntry.second->Accept(); + } + } + pAct->Accept(); + return true; +} + +bool ScChangeTrack::RejectAll() +{ + bool bOk = true; + for ( ScChangeAction* p = GetLast(); p && bOk; p = p->GetPrev() ) + { //TODO: Traverse backwards as dependencies attached to RejectActions + if ( p->IsInternalRejectable() ) + bOk = Reject( p ); + } + return bOk; +} + +bool ScChangeTrack::Reject( ScChangeAction* pAct, bool bShared ) +{ + // #i100895# When collaboration changes are reversed, it must be possible + // to reject a deleted row above another deleted row. + if ( bShared && pAct->IsDeletedIn() ) + pAct->RemoveAllDeletedIn(); + + if ( !pAct->IsRejectable() ) + return false; + + std::unique_ptr<ScChangeActionMap> pMap; + if ( pAct->HasDependent() ) + { + pMap.reset(new ScChangeActionMap); + GetDependents( pAct, *pMap, false, true ); + } + bool bRejected = Reject( pAct, pMap.get(), false ); + return bRejected; +} + +bool ScChangeTrack::Reject( + ScChangeAction* pAct, ScChangeActionMap* pMap, bool bRecursion ) +{ + if ( !pAct->IsInternalRejectable() ) + return false; + + bool bOk = true; + bool bRejected = false; + if ( pAct->IsInsertType() ) + { + if ( pAct->HasDependent() && !bRecursion ) + { + OSL_ENSURE( pMap, "ScChangeTrack::Reject: Insert without map" ); + ScChangeActionMap::reverse_iterator itChangeAction; + for (itChangeAction = pMap->rbegin(); + itChangeAction != pMap->rend() && bOk; ++itChangeAction) + { + // Do not restore Contents which would end up being deleted anyways + if ( itChangeAction->second->GetType() == SC_CAT_CONTENT ) + itChangeAction->second->SetRejected(); + else if ( itChangeAction->second->IsDeleteType() ) + itChangeAction->second->Accept(); // Deleted to Nirvana + else + bOk = Reject( itChangeAction->second, nullptr, true ); // Recursion! + } + } + if ( bOk ) + { + bRejected = pAct->Reject( rDoc ); + if ( bRejected ) + { + // pRefDoc NULL := Do not save deleted Cells + AppendDeleteRange( pAct->GetBigRange().MakeRange( rDoc ), nullptr, short(0), + pAct->GetActionNumber() ); + } + } + } + else if ( pAct->IsDeleteType() ) + { + OSL_ENSURE( !pMap, "ScChangeTrack::Reject: Delete with map" ); + ScBigRange aDelRange; + sal_uLong nRejectAction = pAct->GetActionNumber(); + bool bTabDel, bTabDelOk; + if ( pAct->GetType() == SC_CAT_DELETE_TABS ) + { + bTabDel = true; + aDelRange = pAct->GetBigRange(); + bTabDelOk = pAct->Reject( rDoc ); + bOk = bTabDelOk; + if ( bOk ) + { + pAct = pAct->GetPrev(); + bOk = ( pAct && pAct->GetType() == SC_CAT_DELETE_COLS ); + } + } + else + bTabDel = bTabDelOk = false; + ScChangeActionDel* pDel = static_cast<ScChangeActionDel*>(pAct); + if ( bOk ) + { + aDelRange = pDel->GetOverAllRange(); + bOk = aDelRange.IsValid( rDoc ); + } + bool bOneOk = false; + if ( bOk ) + { + ScChangeActionType eActType = pAct->GetType(); + switch ( eActType ) + { + case SC_CAT_DELETE_COLS : + aDelRange.aStart.SetCol( aDelRange.aEnd.Col() ); + break; + case SC_CAT_DELETE_ROWS : + aDelRange.aStart.SetRow( aDelRange.aEnd.Row() ); + break; + case SC_CAT_DELETE_TABS : + aDelRange.aStart.SetTab( aDelRange.aEnd.Tab() ); + break; + default: + { + // added to avoid warnings + } + } + ScChangeAction* p = pAct; + bool bLoop = true; + do + { + pDel = static_cast<ScChangeActionDel*>(p); + bOk = pDel->Reject( rDoc ); + if ( bOk ) + { + if ( bOneOk ) + { + switch ( pDel->GetType() ) + { + case SC_CAT_DELETE_COLS : + aDelRange.aStart.IncCol( -1 ); + break; + case SC_CAT_DELETE_ROWS : + aDelRange.aStart.IncRow( -1 ); + break; + case SC_CAT_DELETE_TABS : + aDelRange.aStart.IncTab( -1 ); + break; + default: + { + // added to avoid warnings + } + } + } + else + bOneOk = true; + } + if ( pDel->IsBaseDelete() ) + bLoop = false; + else + p = p->GetPrev(); + } while ( bOk && bLoop && p && p->GetType() == eActType && + !static_cast<ScChangeActionDel*>(p)->IsTopDelete() ); + } + bRejected = bOk; + if ( bOneOk || (bTabDel && bTabDelOk) ) + { + // Delete Reject made UpdateReference Undo + ScChangeActionIns* pReject = new ScChangeActionIns( &rDoc, + aDelRange.MakeRange( rDoc ) ); + pReject->SetRejectAction( nRejectAction ); + pReject->SetState( SC_CAS_ACCEPTED ); + Append( pReject ); + } + } + else if ( pAct->GetType() == SC_CAT_MOVE ) + { + if ( pAct->HasDependent() && !bRecursion ) + { + OSL_ENSURE( pMap, "ScChangeTrack::Reject: Move without Map" ); + ScChangeActionMap::reverse_iterator itChangeAction; + + for( itChangeAction = pMap->rbegin(); itChangeAction != pMap->rend() && bOk; ++itChangeAction ) + { + bOk = Reject( itChangeAction->second, nullptr, true ); // Recursion! + } + } + if ( bOk ) + { + bRejected = pAct->Reject( rDoc ); + if ( bRejected ) + { + ScChangeActionMove* pReject = new ScChangeActionMove( + pAct->GetBigRange().MakeRange( rDoc ), + static_cast<ScChangeActionMove*>(pAct)->GetFromRange().MakeRange( rDoc ), this ); + pReject->SetRejectAction( pAct->GetActionNumber() ); + pReject->SetState( SC_CAS_ACCEPTED ); + Append( pReject ); + } + } + } + else if ( pAct->GetType() == SC_CAT_CONTENT ) + { + ScRange aRange; + ScChangeActionContent* pReject; + if ( bRecursion ) + pReject = nullptr; + else + { + aRange = pAct->GetBigRange().aStart.MakeAddress( rDoc ); + pReject = new ScChangeActionContent( aRange ); + ScCellValue aCell; + aCell.assign(rDoc, aRange.aStart); + pReject->SetOldValue(aCell, &rDoc, &rDoc); + } + bRejected = pAct->Reject( rDoc ); + if ( bRejected && !bRecursion ) + { + ScCellValue aCell; + aCell.assign(rDoc, aRange.aStart); + pReject->SetNewValue(aCell, &rDoc); + pReject->SetRejectAction( pAct->GetActionNumber() ); + pReject->SetState( SC_CAS_ACCEPTED ); + Append( pReject ); + } + else + delete pReject; + } + else + { + OSL_FAIL( "ScChangeTrack::Reject: say what?" ); + } + + return bRejected; +} + +bool ScChangeTrack::IsLastAction( sal_uLong nNum ) const +{ + return nNum == nActionMax && pLast && pLast->GetActionNumber() == nNum; +} + +sal_uLong ScChangeTrack::AddLoadedGenerated( + const ScCellValue& rNewCell, const ScBigRange& aBigRange, const OUString& sNewValue ) +{ + ScChangeActionContent* pAct = new ScChangeActionContent( --nGeneratedMin, rNewCell, aBigRange, &rDoc, sNewValue ); + if ( pFirstGeneratedDelContent ) + pFirstGeneratedDelContent->pPrev = pAct; + pAct->pNext = pFirstGeneratedDelContent; + pFirstGeneratedDelContent = pAct; + aGeneratedMap.insert( ::std::make_pair( pAct->GetActionNumber(), pAct ) ); + return pAct->GetActionNumber(); +} + +void ScChangeTrack::AppendCloned( ScChangeAction* pAppend ) +{ + aMap.insert( ::std::make_pair( pAppend->GetActionNumber(), pAppend ) ); + if ( !pLast ) + pFirst = pLast = pAppend; + else + { + pLast->pNext = pAppend; + pAppend->pPrev = pLast; + pLast = pAppend; + } +} + +ScChangeTrack* ScChangeTrack::Clone( ScDocument* pDocument ) const +{ + if ( !pDocument ) + { + return nullptr; + } + + std::unique_ptr<ScChangeTrack> pClonedTrack(new ScChangeTrack( *pDocument )); + pClonedTrack->SetTimeNanoSeconds( IsTimeNanoSeconds() ); + + // clone generated actions + ::std::stack< const ScChangeAction* > aGeneratedStack; + const ScChangeAction* pGenerated = GetFirstGenerated(); + while ( pGenerated ) + { + aGeneratedStack.push( pGenerated ); + pGenerated = pGenerated->GetNext(); + } + while ( !aGeneratedStack.empty() ) + { + pGenerated = aGeneratedStack.top(); + aGeneratedStack.pop(); + const ScChangeActionContent& rContent = dynamic_cast<const ScChangeActionContent&>(*pGenerated); + const ScCellValue& rNewCell = rContent.GetNewCell(); + if (!rNewCell.isEmpty()) + { + ScCellValue aClonedNewCell; + aClonedNewCell.assign(rNewCell, *pDocument); + OUString aNewValue = rContent.GetNewString( pDocument ); + pClonedTrack->nGeneratedMin = pGenerated->GetActionNumber() + 1; + pClonedTrack->AddLoadedGenerated(aClonedNewCell, pGenerated->GetBigRange(), aNewValue); + } + } + + // clone actions + const ScChangeAction* pAction = GetFirst(); + while ( pAction ) + { + ScChangeAction* pClonedAction = nullptr; + + switch ( pAction->GetType() ) + { + case SC_CAT_INSERT_COLS: + case SC_CAT_INSERT_ROWS: + case SC_CAT_INSERT_TABS: + { + bool bEndOfList = static_cast<const ScChangeActionIns*>(pAction)->IsEndOfList(); + pClonedAction = new ScChangeActionIns( + pAction->GetActionNumber(), + pAction->GetState(), + pAction->GetRejectAction(), + pAction->GetBigRange(), + pAction->GetUser(), + pAction->GetDateTimeUTC(), + pAction->GetComment(), + pAction->GetType(), + bEndOfList ); + } + break; + case SC_CAT_DELETE_COLS: + case SC_CAT_DELETE_ROWS: + case SC_CAT_DELETE_TABS: + { + const ScChangeActionDel& rDelete = dynamic_cast<const ScChangeActionDel&>(*pAction); + + SCCOLROW nD = 0; + ScChangeActionType eType = pAction->GetType(); + if ( eType == SC_CAT_DELETE_COLS ) + { + nD = static_cast< SCCOLROW >( rDelete.GetDx() ); + } + else if ( eType == SC_CAT_DELETE_ROWS ) + { + nD = static_cast< SCCOLROW >( rDelete.GetDy() ); + } + + pClonedAction = new ScChangeActionDel( + pAction->GetActionNumber(), + pAction->GetState(), + pAction->GetRejectAction(), + pAction->GetBigRange(), + pAction->GetUser(), + pAction->GetDateTimeUTC(), + pAction->GetComment(), + eType, + nD, + pClonedTrack.get() ); + } + break; + case SC_CAT_MOVE: + { + auto pMove = dynamic_cast<const ScChangeActionMove*>(pAction); + assert(pMove && "ScChangeTrack::Clone: pMove is null!"); + + pClonedAction = new ScChangeActionMove( + pAction->GetActionNumber(), + pAction->GetState(), + pAction->GetRejectAction(), + pAction->GetBigRange(), + pAction->GetUser(), + pAction->GetDateTimeUTC(), + pAction->GetComment(), + pMove->GetFromRange(), + pClonedTrack.get() ); + } + break; + case SC_CAT_CONTENT: + { + const ScChangeActionContent& rContent = dynamic_cast<const ScChangeActionContent&>(*pAction); + const ScCellValue& rOldCell = rContent.GetOldCell(); + ScCellValue aClonedOldCell; + aClonedOldCell.assign(rOldCell, *pDocument); + OUString aOldValue = rContent.GetOldString( pDocument ); + + ScChangeActionContent* pClonedContent = new ScChangeActionContent( + pAction->GetActionNumber(), + pAction->GetState(), + pAction->GetRejectAction(), + pAction->GetBigRange(), + pAction->GetUser(), + pAction->GetDateTimeUTC(), + pAction->GetComment(), + std::move(aClonedOldCell), + pDocument, + aOldValue ); + + const ScCellValue& rNewCell = rContent.GetNewCell(); + if (!rNewCell.isEmpty()) + { + ScCellValue aClonedNewCell; + aClonedNewCell.assign(rNewCell, *pDocument); + pClonedContent->SetNewValue(aClonedNewCell, pDocument); + } + + pClonedAction = pClonedContent; + } + break; + case SC_CAT_REJECT: + { + pClonedAction = new ScChangeActionReject( + pAction->GetActionNumber(), + pAction->GetState(), + pAction->GetRejectAction(), + pAction->GetBigRange(), + pAction->GetUser(), + pAction->GetDateTimeUTC(), + pAction->GetComment() ); + } + break; + default: + { + } + break; + } + + if ( pClonedAction ) + { + pClonedTrack->AppendCloned( pClonedAction ); + } + + pAction = pAction->GetNext(); + } + + if ( pClonedTrack->GetLast() ) + { + pClonedTrack->SetActionMax( pClonedTrack->GetLast()->GetActionNumber() ); + } + + // set dependencies for Deleted/DeletedIn + pAction = GetFirst(); + while ( pAction ) + { + if ( pAction->HasDeleted() ) + { + ::std::stack< sal_uLong > aStack; + const ScChangeActionLinkEntry* pL = pAction->GetFirstDeletedEntry(); + while ( pL ) + { + const ScChangeAction* pDeleted = pL->GetAction(); + if ( pDeleted ) + { + aStack.push( pDeleted->GetActionNumber() ); + } + pL = pL->GetNext(); + } + ScChangeAction* pClonedAction = pClonedTrack->GetAction( pAction->GetActionNumber() ); + if ( pClonedAction ) + { + while ( !aStack.empty() ) + { + ScChangeAction* pClonedDeleted = pClonedTrack->GetActionOrGenerated( aStack.top() ); + aStack.pop(); + if ( pClonedDeleted ) + { + pClonedDeleted->SetDeletedIn( pClonedAction ); + } + } + } + } + pAction = pAction->GetNext(); + } + + // set dependencies for Dependent/Any + pAction = GetLast(); + while ( pAction ) + { + if ( pAction->HasDependent() ) + { + ::std::stack< sal_uLong > aStack; + const ScChangeActionLinkEntry* pL = pAction->GetFirstDependentEntry(); + while ( pL ) + { + const ScChangeAction* pDependent = pL->GetAction(); + if ( pDependent ) + { + aStack.push( pDependent->GetActionNumber() ); + } + pL = pL->GetNext(); + } + ScChangeAction* pClonedAction = pClonedTrack->GetAction( pAction->GetActionNumber() ); + if ( pClonedAction ) + { + while ( !aStack.empty() ) + { + ScChangeAction* pClonedDependent = pClonedTrack->GetActionOrGenerated( aStack.top() ); + aStack.pop(); + if ( pClonedDependent ) + { + ScChangeActionLinkEntry* pLink = pClonedAction->AddDependent( pClonedDependent ); + pClonedDependent->AddLink( pClonedAction, pLink ); + } + } + } + } + pAction = pAction->GetPrev(); + } + + // masterlinks + ScChangeAction* pClonedAction = pClonedTrack->GetFirst(); + while ( pClonedAction ) + { + pClonedTrack->MasterLinks( pClonedAction ); + pClonedAction = pClonedAction->GetNext(); + } + + if ( IsProtected() ) + { + pClonedTrack->SetProtection( GetProtection() ); + } + + if ( pClonedTrack->GetLast() ) + { + pClonedTrack->SetLastSavedActionNumber( pClonedTrack->GetLast()->GetActionNumber() ); + } + + auto tmp = pClonedTrack.get(); + pDocument->SetChangeTrack( std::move(pClonedTrack) ); + + return tmp; +} + +void ScChangeTrack::MergeActionState( ScChangeAction* pAct, const ScChangeAction* pOtherAct ) +{ + if ( !pAct->IsVirgin() ) + return; + + if ( pOtherAct->IsAccepted() ) + { + pAct->Accept(); + if ( pOtherAct->IsRejecting() ) + { + pAct->SetRejectAction( pOtherAct->GetRejectAction() ); + } + } + else if ( pOtherAct->IsRejected() ) + { + pAct->SetRejected(); + } +} + +/// Get info about a single ScChangeAction element. +static void lcl_getTrackedChange(ScDocument& rDoc, int nIndex, const ScChangeAction* pAction, tools::JsonWriter& rRedlines) +{ + if (pAction->GetType() != SC_CAT_CONTENT) + return; + + auto redlinesNode = rRedlines.startStruct(); + rRedlines.put("index", static_cast<sal_Int64>(nIndex)); + + rRedlines.put("author", pAction->GetUser()); + + rRedlines.put("type", "Modify"); + + rRedlines.put("comment", pAction->GetComment()); + + OUString aDescription = pAction->GetDescription(rDoc, true); + rRedlines.put("description", aDescription); + + OUString sDateTime = utl::toISO8601(pAction->GetDateTimeUTC().GetUNODateTime()); + rRedlines.put("dateTime", sDateTime); +} + +void ScChangeTrack::GetChangeTrackInfo(tools::JsonWriter& aRedlines) +{ + auto redlinesNode = aRedlines.startArray("redlines"); + + ScChangeAction* pAction = GetFirst(); + if (pAction) + { + int i = 0; + lcl_getTrackedChange(rDoc, i++, pAction, aRedlines); + ScChangeAction* pLastAction = GetLast(); + while (pAction != pLastAction) + { + pAction = pAction->GetNext(); + lcl_getTrackedChange(rDoc, i++, pAction, aRedlines); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/chgviset.cxx b/sc/source/core/tool/chgviset.cxx new file mode 100644 index 0000000000..0a394cc869 --- /dev/null +++ b/sc/source/core/tool/chgviset.cxx @@ -0,0 +1,150 @@ +/* -*- 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 <unotools/textsearch.hxx> + +#include <chgviset.hxx> +#include <chgtrack.hxx> +#include <document.hxx> + +ScChangeViewSettings::~ScChangeViewSettings() +{ +} + +ScChangeViewSettings::ScChangeViewSettings( const ScChangeViewSettings& r ): + aFirstDateTime( DateTime::EMPTY ), + aLastDateTime( DateTime::EMPTY ) +{ + SetTheComment(r.aComment); + + aFirstDateTime =r.aFirstDateTime; + aLastDateTime =r.aLastDateTime; + aAuthorToShow =r.aAuthorToShow; + aRangeList =r.aRangeList; + eDateMode =r.eDateMode; + bShowIt =r.bShowIt; + bIsDate =r.bIsDate; + bIsAuthor =r.bIsAuthor; + bIsComment =r.bIsComment; + bIsRange =r.bIsRange; + bShowAccepted =r.bShowAccepted; + bShowRejected =r.bShowRejected; + mbIsActionRange = r.mbIsActionRange; + mnFirstAction = r.mnFirstAction; + mnLastAction = r.mnLastAction; + +} + +ScChangeViewSettings& ScChangeViewSettings::operator=( const ScChangeViewSettings& r ) +{ + pCommentSearcher = nullptr; + SetTheComment(r.aComment); + + aFirstDateTime =r.aFirstDateTime; + aLastDateTime =r.aLastDateTime; + aAuthorToShow =r.aAuthorToShow; + aRangeList =r.aRangeList; + eDateMode =r.eDateMode; + bShowIt =r.bShowIt; + bIsDate =r.bIsDate; + bIsAuthor =r.bIsAuthor; + bIsComment =r.bIsComment; + bIsRange =r.bIsRange; + bShowAccepted =r.bShowAccepted; + bShowRejected =r.bShowRejected; + mbIsActionRange = r.mbIsActionRange; + mnFirstAction = r.mnFirstAction; + mnLastAction = r.mnLastAction; + + return *this; +} + +bool ScChangeViewSettings::IsValidComment(const OUString* pCommentStr) const +{ + bool bTheFlag = true; + + if(pCommentSearcher) + { + sal_Int32 nStartPos = 0; + sal_Int32 nEndPos = pCommentStr->getLength(); + bTheFlag = pCommentSearcher->SearchForward(*pCommentStr, &nStartPos, &nEndPos); + } + return bTheFlag; +} + +void ScChangeViewSettings::SetTheComment(const OUString& rString) +{ + aComment = rString; + pCommentSearcher.reset(); + + if(!rString.isEmpty()) + { + utl::SearchParam aSearchParam( rString, + utl::SearchParam::SearchType::Regexp,false ); + + pCommentSearcher.reset( new utl::TextSearch( aSearchParam, ScGlobal::getCharClass() ) ); + } +} + +void ScChangeViewSettings::AdjustDateMode( const ScDocument& rDoc ) +{ + switch ( eDateMode ) + { // corresponds with ScViewUtil::IsActionShown + case SvxRedlinDateMode::EQUAL : + case SvxRedlinDateMode::NOTEQUAL : + aFirstDateTime.SetTime( 0 ); + aLastDateTime = aFirstDateTime; + aLastDateTime.SetTime( 23595999 ); + break; + case SvxRedlinDateMode::SAVE: + { + const ScChangeAction* pLast = nullptr; + ScChangeTrack* pTrack = rDoc.GetChangeTrack(); + if ( pTrack ) + { + pLast = pTrack->GetLastSaved(); + if ( pLast ) + { + aFirstDateTime = pLast->GetDateTime(); + + // Set the next minute as the start time and assume that + // the document isn't saved, reloaded, edited and filter set + // all together during the gap between those two times. + aFirstDateTime += tools::Time( 0, 1 ); + aFirstDateTime.SetSec(0); + aFirstDateTime.SetNanoSec(0); + } + } + if ( !pLast ) + { + aFirstDateTime.SetDate( 18990101 ); + aFirstDateTime.SetTime( 0 ); + } + aLastDateTime = Date( Date::SYSTEM ); + aLastDateTime.AddYears( 100 ); + } + break; + default: + { + // added to avoid warnings + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/compare.cxx b/sc/source/core/tool/compare.cxx new file mode 100644 index 0000000000..58ed51f65f --- /dev/null +++ b/sc/source/core/tool/compare.cxx @@ -0,0 +1,334 @@ +/* -*- 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 <compare.hxx> + +#include <document.hxx> +#include <docoptio.hxx> + +#include <unotools/collatorwrapper.hxx> +#include <unotools/textsearch.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <rtl/math.hxx> +#include <osl/diagnose.h> + +namespace sc { + +Compare::Cell::Cell() : + mfValue(0.0), mbValue(false), mbEmpty(false) {} + +Compare::Compare() : + meOp(SC_EQUAL), mbIgnoreCase(true) {} + +CompareOptions::CompareOptions( const ScDocument& rDoc, const ScQueryEntry& rEntry, utl::SearchParam::SearchType eSrchTyp ) : + aQueryEntry(rEntry), + eSearchType(eSrchTyp), + bMatchWholeCell(rDoc.GetDocOptions().IsMatchWholeCell()) +{ + // Wildcard and Regex search work only with equal or not equal. + if (eSearchType != utl::SearchParam::SearchType::Normal && + aQueryEntry.eOp != SC_EQUAL && aQueryEntry.eOp != SC_NOT_EQUAL) + eSearchType = utl::SearchParam::SearchType::Normal; + + // Interpreter functions usually are case insensitive, except the simple + // comparison operators, for which these options aren't used. Override in + // struct if needed. +} + +double CompareFunc( const Compare& rComp, CompareOptions* pOptions ) +{ + const Compare::Cell& rCell1 = rComp.maCells[0]; + const Compare::Cell& rCell2 = rComp.maCells[1]; + + // Keep DoubleError if encountered + // #i40539# if bEmpty is set, bVal/nVal are uninitialized + if (!rCell1.mbEmpty && rCell1.mbValue && !std::isfinite(rCell1.mfValue)) + return rCell1.mfValue; + if (!rCell2.mbEmpty && rCell2.mbValue && !std::isfinite(rCell2.mfValue)) + return rCell2.mfValue; + + size_t nStringQuery = 0; // 0:=no, 1:=0, 2:=1 + double fRes = 0; + if (rCell1.mbEmpty) + { + if (rCell2.mbEmpty) + ; // empty cell == empty cell, fRes 0 + else if (rCell2.mbValue) + { + if (rCell2.mfValue != 0.0) + { + if (rCell2.mfValue < 0.0) + fRes = 1; // empty cell > -x + else + fRes = -1; // empty cell < x + } + // else: empty cell == 0.0 + } + else + { + if (!rCell2.maStr.isEmpty()) + fRes = -1; // empty cell < "..." + // else: empty cell == "" + } + } + else if (rCell2.mbEmpty) + { + if (rCell1.mbValue) + { + if (rCell1.mfValue != 0.0) + { + if (rCell1.mfValue < 0.0) + fRes = -1; // -x < empty cell + else + fRes = 1; // x > empty cell + } + // else: empty cell == 0.0 + } + else + { + if (!rCell1.maStr.isEmpty()) + fRes = 1; // "..." > empty cell + // else: "" == empty cell + } + } + else if (rCell1.mbValue) + { + if (rCell2.mbValue) + { + if (!rtl::math::approxEqual(rCell1.mfValue, rCell2.mfValue)) + { + if (rCell1.mfValue - rCell2.mfValue < 0) + fRes = -1; + else + fRes = 1; + } + } + else + { + fRes = -1; // number is less than string + nStringQuery = 2; // 1+1 + } + } + else if (rCell2.mbValue) + { + fRes = 1; // string is greater than number + nStringQuery = 1; // 0+1 + } + else + { + // Both strings. + if (pOptions) + { + // All similar to ScTable::ValidQuery(), *rComp.pVal[1] actually + // is/must be identical to *rEntry.pStr, which is essential for + // regex to work through GetSearchTextPtr(). + ScQueryEntry& rEntry = pOptions->aQueryEntry; + OSL_ENSURE(rEntry.GetQueryItem().maString == rCell2.maStr, "ScInterpreter::CompareFunc: broken options"); + if (pOptions->eSearchType != utl::SearchParam::SearchType::Normal) + { + sal_Int32 nStart = 0; + sal_Int32 nStop = rCell1.maStr.getLength(); + bool bMatch = rEntry.GetSearchTextPtr( pOptions->eSearchType, !rComp.mbIgnoreCase, + pOptions->bMatchWholeCell)->SearchForward( rCell1.maStr.getString(), &nStart, &nStop); + if (bMatch && pOptions->bMatchWholeCell && (nStart != 0 || nStop != rCell1.maStr.getLength())) + bMatch = false; // RegEx must match entire string. + fRes = (bMatch ? 0 : 1); + } + else if (rEntry.eOp == SC_EQUAL || rEntry.eOp == SC_NOT_EQUAL) + { + ::utl::TransliterationWrapper& rTransliteration = + ScGlobal::GetTransliteration(!rComp.mbIgnoreCase); + bool bMatch = false; + if (pOptions->bMatchWholeCell) + { + if (rComp.mbIgnoreCase) + bMatch = rCell1.maStr.getDataIgnoreCase() == rCell2.maStr.getDataIgnoreCase(); + else + bMatch = rCell1.maStr.getData() == rCell2.maStr.getData(); + } + else + { + const LanguageType nLang = ScGlobal::oSysLocale->GetLanguageTag().getLanguageType(); + OUString aCell( rTransliteration.transliterate( + rCell1.maStr.getString(), nLang, 0, + rCell1.maStr.getLength(), nullptr)); + OUString aQuer( rTransliteration.transliterate( + rCell2.maStr.getString(), nLang, 0, + rCell2.maStr.getLength(), nullptr)); + bMatch = (aCell.indexOf( aQuer ) != -1); + } + fRes = (bMatch ? 0 : 1); + } + else if (rComp.mbIgnoreCase) + fRes = static_cast<double>(ScGlobal::GetCollator().compareString( + rCell1.maStr.getString(), rCell2.maStr.getString())); + else + fRes = static_cast<double>(ScGlobal::GetCaseCollator().compareString( + rCell1.maStr.getString(), rCell2.maStr.getString())); + } + else if (rComp.meOp == SC_EQUAL || rComp.meOp == SC_NOT_EQUAL) + { + if (rComp.mbIgnoreCase) + fRes = (rCell1.maStr.getDataIgnoreCase() == rCell2.maStr.getDataIgnoreCase()) ? 0 : 1; + else + fRes = (rCell1.maStr.getData() == rCell2.maStr.getData()) ? 0 : 1; + } + else if (rComp.mbIgnoreCase) + fRes = static_cast<double>(ScGlobal::GetCollator().compareString( + rCell1.maStr.getString(), rCell2.maStr.getString())); + else + fRes = static_cast<double>(ScGlobal::GetCaseCollator().compareString( + rCell1.maStr.getString(), rCell2.maStr.getString())); + } + + if (nStringQuery && pOptions) + { + const ScQueryEntry& rEntry = pOptions->aQueryEntry; + const ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems(); + if (!rItems.empty()) + { + const ScQueryEntry::Item& rItem = rItems[0]; + if (rItem.meType != ScQueryEntry::ByString && !rItem.maString.isEmpty() && + (rEntry.eOp == SC_EQUAL || rEntry.eOp == SC_NOT_EQUAL)) + { + // As in ScTable::ValidQuery() match a numeric string for a + // number query that originated from a string, e.g. in SUMIF + // and COUNTIF. Transliteration is not needed here. + bool bEqual = false; + if (nStringQuery == 1) + bEqual = rCell1.maStr == rItem.maString; + else + bEqual = rCell2.maStr == rItem.maString; + + // match => fRes=0, else fRes=1 + fRes = double((rEntry.eOp == SC_NOT_EQUAL) ? bEqual : !bEqual); + } + } + } + + return fRes; +} + +double CompareFunc( const Compare::Cell& rCell1, double fCell2, const CompareOptions* pOptions ) +{ + // Keep DoubleError if encountered + // #i40539# if bEmpty is set, bVal/nVal are uninitialized + if (!rCell1.mbEmpty && rCell1.mbValue && !std::isfinite(rCell1.mfValue)) + return rCell1.mfValue; + if (!std::isfinite(fCell2)) + return fCell2; + + bool bStringQuery = false; + double fRes = 0; + if (rCell1.mbEmpty) + { + if (fCell2 != 0.0) + { + if (fCell2 < 0.0) + fRes = 1; // empty cell > -x + else + fRes = -1; // empty cell < x + } + // else: empty cell == 0.0 + } + else if (rCell1.mbValue) + { + if (!rtl::math::approxEqual(rCell1.mfValue, fCell2)) + { + if (rCell1.mfValue - fCell2 < 0) + fRes = -1; + else + fRes = 1; + } + } + else + { + fRes = 1; // string is greater than number + bStringQuery = true; + } + + if (bStringQuery && pOptions) + { + const ScQueryEntry& rEntry = pOptions->aQueryEntry; + const ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems(); + if (!rItems.empty()) + { + const ScQueryEntry::Item& rItem = rItems[0]; + if (rItem.meType != ScQueryEntry::ByString && !rItem.maString.isEmpty() && + (rEntry.eOp == SC_EQUAL || rEntry.eOp == SC_NOT_EQUAL)) + { + // As in ScTable::ValidQuery() match a numeric string for a + // number query that originated from a string, e.g. in SUMIF + // and COUNTIF. Transliteration is not needed here. + bool bEqual = rCell1.maStr == rItem.maString; + + // match => fRes=0, else fRes=1 + fRes = double((rEntry.eOp == SC_NOT_EQUAL) ? bEqual : !bEqual); + } + } + } + + return fRes; +} + +double CompareFunc( double fCell1, double fCell2 ) +{ + // Keep DoubleError if encountered + // #i40539# if bEmpty is set, bVal/nVal are uninitialized + if (!std::isfinite(fCell1)) + return fCell1; + if (!std::isfinite(fCell2)) + return fCell2; + + double fRes = 0.0; + + if (!rtl::math::approxEqual(fCell1, fCell2)) + { + if (fCell1 - fCell2 < 0.0) + fRes = -1; + else + fRes = 1; + } + + return fRes; +} + +double CompareEmptyToNumericFunc( double fCell2 ) +{ + // Keep DoubleError if encountered + // #i40539# if bEmpty is set, bVal/nVal are uninitialized + if (!std::isfinite(fCell2)) + return fCell2; + + double fRes = 0; + if (fCell2 != 0.0) + { + if (fCell2 < 0.0) + fRes = 1; // empty cell > -x + else + fRes = -1; // empty cell < x + } + // else: empty cell == 0.0 + + return fRes; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/compiler.cxx b/sc/source/core/tool/compiler.cxx new file mode 100644 index 0000000000..63b1f09692 --- /dev/null +++ b/sc/source/core/tool/compiler.cxx @@ -0,0 +1,6651 @@ +/* -*- 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 <compiler.hxx> + +#include <mutex> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <sfx2/app.hxx> +#include <sfx2/objsh.hxx> +#include <basic/sbmeth.hxx> +#include <basic/sbstar.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <svl/sharedstringpool.hxx> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> +#include <rtl/character.hxx> +#include <unotools/charclass.hxx> +#include <unotools/configmgr.hxx> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/sheet/FormulaOpCodeMapEntry.hpp> +#include <com/sun/star/sheet/FormulaLanguage.hpp> +#include <com/sun/star/i18n/KParseTokens.hpp> +#include <com/sun/star/i18n/KParseType.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <tools/urlobj.hxx> +#include <rtl/math.hxx> +#include <rtl/ustring.hxx> +#include <stdlib.h> +#include <rangenam.hxx> +#include <dbdata.hxx> +#include <document.hxx> +#include <docsh.hxx> +#include <callform.hxx> +#include <addincol.hxx> +#include <refupdat.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <formulacell.hxx> +#include <dociter.hxx> +#include <docoptio.hxx> +#include <formula/errorcodes.hxx> +#include <parclass.hxx> +#include <autonamecache.hxx> +#include <externalrefmgr.hxx> +#include <rangeutl.hxx> +#include <convuno.hxx> +#include <tokenuno.hxx> +#include <formulaparserpool.hxx> +#include <tokenarray.hxx> +#include <scmatrix.hxx> +#include <tokenstringcontext.hxx> +#include <officecfg/Office/Common.hxx> + +using namespace formula; +using namespace ::com::sun::star; +using ::std::vector; + +const CharClass* ScCompiler::pCharClassEnglish = nullptr; +const CharClass* ScCompiler::pCharClassLocalized = nullptr; +const ScCompiler::Convention* ScCompiler::pConventions[ ] = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; + +namespace { + +enum ScanState +{ + ssGetChar, + ssGetBool, + ssGetValue, + ssGetString, + ssSkipString, + ssGetIdent, + ssGetReference, + ssSkipReference, + ssGetErrorConstant, + ssGetTableRefItem, + ssGetTableRefColumn, + ssStop +}; + +} + +static const char* pInternal[2] = { "TTT", "__DEBUG_VAR" }; + +using namespace ::com::sun::star::i18n; + +void ScCompiler::fillFromAddInMap( const NonConstOpCodeMapPtr& xMap,FormulaGrammar::Grammar _eGrammar ) const +{ + size_t nSymbolOffset; + switch( _eGrammar ) + { + // XFunctionAccess and XCell::setFormula()/getFormula() API always used + // PODF grammar symbols, keep it. + case FormulaGrammar::GRAM_API: + case FormulaGrammar::GRAM_PODF: + nSymbolOffset = offsetof( AddInMap, pUpper); + break; + default: + case FormulaGrammar::GRAM_ODFF: + nSymbolOffset = offsetof( AddInMap, pODFF); + break; + case FormulaGrammar::GRAM_ENGLISH: + nSymbolOffset = offsetof( AddInMap, pEnglish); + break; + } + const AddInMap* const pStop = g_aAddInMap + GetAddInMapCount(); + for (const AddInMap* pMap = g_aAddInMap; pMap < pStop; ++pMap) + { + char const * const * ppSymbol = + reinterpret_cast< char const * const * >( + reinterpret_cast< char const * >(pMap) + nSymbolOffset); + xMap->putExternal( OUString::createFromAscii( *ppSymbol), + OUString::createFromAscii( pMap->pOriginal)); + } + if (_eGrammar == FormulaGrammar::GRAM_API) + { + // Add English names additionally to programmatic names, so they + // can be used in XCell::setFormula() non-localized API calls. + // Note the reverse map will still deliver programmatic names for + // XCell::getFormula(). + nSymbolOffset = offsetof( AddInMap, pEnglish); + for (const AddInMap* pMap = g_aAddInMap; pMap < pStop; ++pMap) + { + char const * const * ppSymbol = + reinterpret_cast< char const * const * >( + reinterpret_cast< char const * >(pMap) + nSymbolOffset); + xMap->putExternal( OUString::createFromAscii( *ppSymbol), + OUString::createFromAscii( pMap->pOriginal)); + } + } +} + +void ScCompiler::fillFromAddInCollectionUpperName( const NonConstOpCodeMapPtr& xMap ) const +{ + ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection(); + tools::Long nCount = pColl->GetFuncCount(); + for (tools::Long i=0; i < nCount; ++i) + { + const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i); + if (pFuncData) + xMap->putExternalSoftly( pFuncData->GetUpperName(), + pFuncData->GetOriginalName()); + } +} + +void ScCompiler::fillFromAddInCollectionEnglishName( const NonConstOpCodeMapPtr& xMap ) const +{ + ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection(); + tools::Long nCount = pColl->GetFuncCount(); + for (tools::Long i=0; i < nCount; ++i) + { + const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i); + if (pFuncData) + { + const OUString aName( pFuncData->GetUpperEnglish()); + if (!aName.isEmpty()) + xMap->putExternalSoftly( aName, pFuncData->GetOriginalName()); + else + xMap->putExternalSoftly( pFuncData->GetUpperName(), + pFuncData->GetOriginalName()); + } + } +} + +void ScCompiler::DeInit() +{ + if (pCharClassEnglish) + { + delete pCharClassEnglish; + pCharClassEnglish = nullptr; + } + if (pCharClassLocalized) + { + delete pCharClassLocalized; + pCharClassLocalized = nullptr; + } +} + +bool ScCompiler::IsEnglishSymbol( const OUString& rName ) +{ + // function names are always case-insensitive + OUString aUpper = GetCharClassEnglish()->uppercase(rName); + + // 1. built-in function name + formula::FormulaCompiler aCompiler; + OpCode eOp = aCompiler.GetEnglishOpCode( aUpper ); + if ( eOp != ocNone ) + { + return true; + } + // 2. old add in functions + if (ScGlobal::GetLegacyFuncCollection()->findByName(aUpper)) + { + return true; + } + + // 3. new (uno) add in functions + OUString aIntName = ScGlobal::GetAddInCollection()->FindFunction(aUpper, false); + return !aIntName.isEmpty(); // no valid function name +} + +static std::mutex& getCharClassMutex() +{ + static std::mutex aMutex; + return aMutex; +} + +const CharClass* ScCompiler::GetCharClassEnglish() +{ + if (!pCharClassEnglish) + { + std::scoped_lock aGuard(getCharClassMutex()); + if (!pCharClassEnglish) + { + pCharClassEnglish = new CharClass( ::comphelper::getProcessComponentContext(), + LanguageTag( LANGUAGE_ENGLISH_US)); + } + } + return pCharClassEnglish; +} + +const CharClass* ScCompiler::GetCharClassLocalized() +{ + if (!pCharClassLocalized) + { + // Switching UI language requires restart; if not, we would have to + // keep track of that. + std::scoped_lock aGuard(getCharClassMutex()); + if (!pCharClassLocalized) + { + pCharClassLocalized = new CharClass( ::comphelper::getProcessComponentContext(), + Application::GetSettings().GetUILanguageTag()); + } + } + return pCharClassLocalized; +} + +void ScCompiler::SetGrammar( const FormulaGrammar::Grammar eGrammar ) +{ + assert( eGrammar != FormulaGrammar::GRAM_UNSPECIFIED && "ScCompiler::SetGrammar: don't pass FormulaGrammar::GRAM_UNSPECIFIED"); + if (eGrammar == GetGrammar()) + return; // nothing to be done + + if( eGrammar == FormulaGrammar::GRAM_EXTERNAL ) + { + meGrammar = eGrammar; + mxSymbols = GetFinalOpCodeMap( css::sheet::FormulaLanguage::NATIVE); + } + else + { + FormulaGrammar::Grammar eMyGrammar = eGrammar; + const sal_Int32 nFormulaLanguage = FormulaGrammar::extractFormulaLanguage( eMyGrammar); + OpCodeMapPtr xMap = GetFinalOpCodeMap( nFormulaLanguage); + OSL_ENSURE( xMap, "ScCompiler::SetGrammar: unknown formula language"); + if (!xMap) + { + xMap = GetFinalOpCodeMap( css::sheet::FormulaLanguage::NATIVE); + eMyGrammar = xMap->getGrammar(); + } + + // Save old grammar for call to SetGrammarAndRefConvention(). + FormulaGrammar::Grammar eOldGrammar = GetGrammar(); + // This also sets the grammar associated with the map! + SetFormulaLanguage( xMap); + + // Override if necessary. + if (eMyGrammar != GetGrammar()) + SetGrammarAndRefConvention( eMyGrammar, eOldGrammar); + } +} + +// Unclear how this was intended to be refreshed when the +// grammar or sheet count is changed ? Ideally this would be +// a method on Document that would globally cache these. +std::vector<OUString> &ScCompiler::GetSetupTabNames() const +{ + std::vector<OUString> &rTabNames = const_cast<ScCompiler *>(this)->maTabNames; + + if (rTabNames.empty()) + { + rTabNames = rDoc.GetAllTableNames(); + for (auto& rTabName : rTabNames) + ScCompiler::CheckTabQuotes(rTabName, formula::FormulaGrammar::extractRefConvention(meGrammar)); + } + + return rTabNames; +} + +void ScCompiler::SetNumberFormatter( SvNumberFormatter* pFormatter ) +{ + mpFormatter = pFormatter; +} + +void ScCompiler::SetFormulaLanguage( const ScCompiler::OpCodeMapPtr & xMap ) +{ + if (!xMap) + return; + + mxSymbols = xMap; + if (mxSymbols->isEnglish()) + pCharClass = GetCharClassEnglish(); + else + pCharClass = GetCharClassLocalized(); + + // The difference is needed for an uppercase() call that usually does not + // result in different strings but for a few languages like Turkish; + // though even de-DE and de-CH may differ in ß/SS handling.. + // At least don't care if both are English. + // The current locale is more likely to not be "en" so check first. + const LanguageTag& rLT1 = ScGlobal::getCharClass().getLanguageTag(); + const LanguageTag& rLT2 = pCharClass->getLanguageTag(); + mbCharClassesDiffer = (rLT1 != rLT2 && (rLT1.getLanguage() != "en" || rLT2.getLanguage() != "en")); + + SetGrammarAndRefConvention( mxSymbols->getGrammar(), GetGrammar()); +} + +void ScCompiler::SetGrammarAndRefConvention( + const FormulaGrammar::Grammar eNewGrammar, const FormulaGrammar::Grammar eOldGrammar ) +{ + meGrammar = eNewGrammar; // SetRefConvention needs the new grammar set! + FormulaGrammar::AddressConvention eConv = FormulaGrammar::extractRefConvention( meGrammar); + if (eConv == FormulaGrammar::CONV_UNSPECIFIED && eOldGrammar == FormulaGrammar::GRAM_UNSPECIFIED) + SetRefConvention( rDoc.GetAddressConvention()); + else + SetRefConvention( eConv ); +} + +OUString ScCompiler::FindAddInFunction( const OUString& rUpperName, bool bLocalFirst ) const +{ + return ScGlobal::GetAddInCollection()->FindFunction(rUpperName, bLocalFirst); // bLocalFirst=false for english +} + +ScCompiler::Convention::~Convention() +{ +} + +ScCompiler::Convention::Convention( FormulaGrammar::AddressConvention eConv ) + : + meConv( eConv ) +{ + int i; + ScCharFlags *t= new ScCharFlags [128]; + + ScCompiler::pConventions[ meConv ] = this; + mpCharTable.reset( t ); + + for (i = 0; i < 128; i++) + t[i] = ScCharFlags::Illegal; + +// Allow tabs/newlines. +// Allow saving whitespace as is (as per OpenFormula specification v.1.2, clause 5.14 "Whitespace"). +/* tab */ t[ 9] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep; +/* lf */ t[10] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep; +/* cr */ t[13] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep; + +/* */ t[32] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep; +/* ! */ t[33] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; + if (FormulaGrammar::CONV_ODF == meConv) +/* ! */ t[33] |= ScCharFlags::OdfLabelOp; +/* " */ t[34] = ScCharFlags::CharString | ScCharFlags::StringSep; +/* # */ t[35] = ScCharFlags::WordSep | ScCharFlags::CharErrConst; +/* $ */ t[36] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident; + if (FormulaGrammar::CONV_ODF == meConv) +/* $ */ t[36] |= ScCharFlags::OdfNameMarker; +/* % */ t[37] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; +/* & */ t[38] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; +/* ' */ t[39] = ScCharFlags::NameSep; +/* ( */ t[40] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; +/* ) */ t[41] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; +/* * */ t[42] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; +/* + */ t[43] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueExp | ScCharFlags::ValueSign; +/* , */ t[44] = ScCharFlags::CharValue | ScCharFlags::Value; +/* - */ t[45] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueExp | ScCharFlags::ValueSign; +/* . */ t[46] = ScCharFlags::Word | ScCharFlags::CharValue | ScCharFlags::Value | ScCharFlags::Ident | ScCharFlags::Name; +/* / */ t[47] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; + + for (i = 48; i < 58; i++) +/* 0-9 */ t[i] = ScCharFlags::CharValue | ScCharFlags::Word | ScCharFlags::Value | ScCharFlags::ValueExp | ScCharFlags::ValueValue | ScCharFlags::Ident | ScCharFlags::Name; + +/* : */ t[58] = ScCharFlags::Char | ScCharFlags::Word; +/* ; */ t[59] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; +/* < */ t[60] = ScCharFlags::CharBool | ScCharFlags::WordSep | ScCharFlags::ValueSep; +/* = */ t[61] = ScCharFlags::Char | ScCharFlags::Bool | ScCharFlags::WordSep | ScCharFlags::ValueSep; +/* > */ t[62] = ScCharFlags::CharBool | ScCharFlags::Bool | ScCharFlags::WordSep | ScCharFlags::ValueSep; +/* ? */ t[63] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::Name; +/* @ */ // FREE + + for (i = 65; i < 91; i++) +/* A-Z */ t[i] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident | ScCharFlags::CharName | ScCharFlags::Name; + + if (FormulaGrammar::CONV_ODF == meConv) + { +/* [ */ t[91] = ScCharFlags::OdfLBracket; +/* \ */ // FREE +/* ] */ t[93] = ScCharFlags::OdfRBracket; + } + else if (FormulaGrammar::CONV_OOO == meConv) + { +/* [ */ t[91] = ScCharFlags::Char; +/* \ */ // FREE +/* ] */ t[93] = ScCharFlags::Char; + } + else if (FormulaGrammar::CONV_XL_OOX == meConv) + { +/* [ */ t[91] = ScCharFlags::Char | ScCharFlags::CharIdent; +/* \ */ // FREE +/* ] */ t[93] = ScCharFlags::Char | ScCharFlags::Ident; + } + else if (FormulaGrammar::CONV_XL_A1 == meConv) + { +/* [ */ t[91] = ScCharFlags::Char; +/* \ */ // FREE +/* ] */ t[93] = ScCharFlags::Char; + } + else if( FormulaGrammar::CONV_XL_R1C1 == meConv ) + { +/* [ */ t[91] = ScCharFlags::Ident; +/* \ */ // FREE +/* ] */ t[93] = ScCharFlags::Ident; + } + else + { +/* [ */ // FREE +/* \ */ // FREE +/* ] */ // FREE + } + +/* ^ */ t[94] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; +/* _ */ t[95] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident | ScCharFlags::CharName | ScCharFlags::Name; +/* ` */ // FREE + + for (i = 97; i < 123; i++) +/* a-z */ t[i] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident | ScCharFlags::CharName | ScCharFlags::Name; + +/* { */ t[123] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; // array open +/* | */ t[124] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; // array row sep (Should be OOo specific) +/* } */ t[125] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; // array close +/* ~ */ t[126] = ScCharFlags::Char; // OOo specific +/* 127 */ // FREE + + if( !(FormulaGrammar::CONV_XL_A1 == meConv || FormulaGrammar::CONV_XL_R1C1 == meConv || FormulaGrammar::CONV_XL_OOX == meConv) ) +return; + +/* */ t[32] |= ScCharFlags::Word; +/* ! */ t[33] |= ScCharFlags::Ident | ScCharFlags::Word; +/* " */ t[34] |= ScCharFlags::Word; +/* # */ t[35] &= ~ScCharFlags::WordSep; +/* # */ t[35] |= ScCharFlags::Word; +/* % */ t[37] |= ScCharFlags::Word; +/* & */ t[38] |= ScCharFlags::Word; +/* ' */ t[39] |= ScCharFlags::Word; +/* ( */ t[40] |= ScCharFlags::Word; +/* ) */ t[41] |= ScCharFlags::Word; +/* * */ t[42] |= ScCharFlags::Word; +/* + */ t[43] |= ScCharFlags::Word; +#if 0 /* this really needs to be locale specific. */ +/* , */ t[44] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; +#else +/* , */ t[44] |= ScCharFlags::Word; +#endif +/* - */ t[45] |= ScCharFlags::Word; + +/* ; */ t[59] |= ScCharFlags::Word; +/* < */ t[60] |= ScCharFlags::Word; +/* = */ t[61] |= ScCharFlags::Word; +/* > */ t[62] |= ScCharFlags::Word; +/* ? */ // question really is not permitted in sheet name +/* @ */ t[64] |= ScCharFlags::Word; +/* [ */ t[91] |= ScCharFlags::Word; +/* ] */ t[93] |= ScCharFlags::Word; +/* { */ t[123]|= ScCharFlags::Word; +/* | */ t[124]|= ScCharFlags::Word; +/* } */ t[125]|= ScCharFlags::Word; +/* ~ */ t[126]|= ScCharFlags::Word; +} + +static bool lcl_isValidQuotedText( std::u16string_view rFormula, size_t nSrcPos, ParseResult& rRes ) +{ + // Tokens that start at ' can have anything in them until a final ' + // but '' marks an escaped ' + // We've earlier guaranteed that a string containing '' will be + // surrounded by ' + if (nSrcPos < rFormula.size() && rFormula[nSrcPos] == '\'') + { + size_t nPos = nSrcPos+1; + while (nPos < rFormula.size()) + { + if (rFormula[nPos] == '\'') + { + if ( (nPos+1 == rFormula.size()) || (rFormula[nPos+1] != '\'') ) + { + rRes.TokenType = KParseType::SINGLE_QUOTE_NAME; + rRes.EndPos = nPos+1; + return true; + } + ++nPos; + } + ++nPos; + } + } + + return false; +} + +static bool lcl_parseExternalName( + const OUString& rSymbol, + OUString& rFile, + OUString& rName, + const sal_Unicode cSep, + const ScDocument& rDoc, + const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks ) +{ + /* TODO: future versions will have to support sheet-local names too, thus + * return a possible sheet name as well. */ + const sal_Unicode* const pStart = rSymbol.getStr(); + const sal_Unicode* p = pStart; + sal_Int32 nLen = rSymbol.getLength(); + OUString aTmpFile; + OUStringBuffer aTmpName; + sal_Int32 i = 0; + bool bInName = false; + if (cSep == '!') + { + // For XL use existing parser that resolves bracketed and quoted and + // indexed external document names. + ScRange aRange; + OUString aStartTabName, aEndTabName; + ScRefFlags nFlags = ScRefFlags::ZERO; + p = aRange.Parse_XL_Header( p, rDoc, aTmpFile, aStartTabName, + aEndTabName, nFlags, true, pExternalLinks ); + if (!p || p == pStart) + return false; + i = sal_Int32(p - pStart); + } + for ( ; i < nLen; ++i, ++p) + { + sal_Unicode c = *p; + if (i == 0) + { + if (c == '.' || c == cSep) + return false; + + if (c == '\'') + { + // Move to the next char and loop until the second single + // quote. + sal_Unicode cPrev = c; + ++i; ++p; + for (sal_Int32 j = i; j < nLen; ++j, ++p) + { + c = *p; + if (c == '\'') + { + if (j == i) + { + // empty quote e.g. (=''!Name) + return false; + } + + if (cPrev == '\'') + { + // two consecutive quotes equal a single quote in + // the file name. + aTmpFile += OUStringChar(c); + cPrev = 'a'; + } + else + cPrev = c; + + continue; + } + + if (cPrev == '\'' && j != i) + { + // this is not a quote but the previous one is. This + // ends the parsing of the quoted segment. At this + // point, the current char must equal the separator + // char. + + i = j; + bInName = true; + aTmpName.append(c); // Keep the separator as part of the name. + break; + } + aTmpFile += OUStringChar(c); + cPrev = c; + } + + if (!bInName) + { + // premature ending of the quoted segment. + return false; + } + + if (c != cSep) + { + // only the separator is allowed after the closing quote. + return false; + } + + continue; + } + } + + if (bInName) + { + if (c == cSep) + { + // A second separator ? Not a valid external name. + return false; + } + aTmpName.append(c); + } + else + { + if (c == cSep) + { + bInName = true; + aTmpName.append(c); // Keep the separator as part of the name. + } + else + { + do + { + if (rtl::isAsciiAlphanumeric(c)) + // allowed. + break; + + if (c > 128) + // non-ASCII character is allowed. + break; + + bool bValid = false; + switch (c) + { + case '_': + case '-': + case '.': + // these special characters are allowed. + bValid = true; + break; + } + if (bValid) + break; + + return false; + } + while (false); + aTmpFile += OUStringChar(c); + } + } + } + + if (!bInName) + { + // No name found - most likely the symbol has no '!'s. + return false; + } + + sal_Int32 nNameLen = aTmpName.getLength(); + if (nNameLen < 2) + { + // Name must be at least 2-char long (separator plus name). + return false; + } + + if (aTmpName[0] != cSep) + { + // 1st char of the name must equal the separator. + return false; + } + + if (aTmpName[nNameLen-1] == '!') + { + // Check against #REF!. + if (OUString::unacquired(aTmpName).equalsIgnoreAsciiCase("#REF!")) + return false; + } + + rFile = aTmpFile; + rName = aTmpName.makeStringAndClear().copy(1); // Skip the first char as it is always the separator. + return true; +} + +static OUString lcl_makeExternalNameStr(const OUString& rFile, const OUString& rName, + const sal_Unicode cSep, bool bODF ) +{ + OUString aEscQuote("''"); + OUString aFile(rFile.replaceAll("'", aEscQuote)); + OUString aName(rName); + if (bODF) + aName = aName.replaceAll("'", aEscQuote); + OUStringBuffer aBuf(aFile.getLength() + aName.getLength() + 9); + if (bODF) + aBuf.append( '['); + aBuf.append( "'" + aFile + "'" + OUStringChar(cSep) ); + if (bODF) + aBuf.append( "$$'" ); + aBuf.append( aName); + if (bODF) + aBuf.append( "']" ); + return aBuf.makeStringAndClear(); +} + +static bool lcl_getLastTabName( OUString& rTabName2, const OUString& rTabName1, + const vector<OUString>& rTabNames, const ScRange& rRef ) +{ + SCTAB nTabSpan = rRef.aEnd.Tab() - rRef.aStart.Tab(); + if (nTabSpan > 0) + { + size_t nCount = rTabNames.size(); + vector<OUString>::const_iterator itrBeg = rTabNames.begin(), itrEnd = rTabNames.end(); + vector<OUString>::const_iterator itr = ::std::find(itrBeg, itrEnd, rTabName1); + if (itr == rTabNames.end()) + { + rTabName2 = ScResId(STR_NO_REF_TABLE); + return false; + } + + size_t nDist = ::std::distance(itrBeg, itr); + if (nDist + static_cast<size_t>(nTabSpan) >= nCount) + { + rTabName2 = ScResId(STR_NO_REF_TABLE); + return false; + } + + rTabName2 = rTabNames[nDist+nTabSpan]; + } + else + rTabName2 = rTabName1; + + return true; +} + +namespace { + +struct Convention_A1 : public ScCompiler::Convention +{ + explicit Convention_A1( FormulaGrammar::AddressConvention eConv ) : ScCompiler::Convention( eConv ) { } + static void MakeColStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCCOL nCol ); + static void MakeRowStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCROW nRow ); + + ParseResult parseAnyToken( const OUString& rFormula, + sal_Int32 nSrcPos, + const CharClass* pCharClass, + bool bGroupSeparator) const override + { + ParseResult aRet; + if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) ) + return aRet; + + constexpr sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER | + KParseTokens::ASC_UNDERSCORE | KParseTokens::ASC_DOLLAR; + constexpr sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT; + // '?' allowed in range names because of Xcl :-/ + static constexpr OUString aAddAllowed(u"?#"_ustr); + return pCharClass->parseAnyToken( rFormula, + nSrcPos, nStartFlags, aAddAllowed, + (bGroupSeparator ? nContFlags | KParseTokens::GROUP_SEPARATOR_IN_NUMBER : nContFlags), + aAddAllowed ); + } + + virtual ScCharFlags getCharTableFlags( sal_Unicode c, sal_Unicode /*cLast*/ ) const override + { + return mpCharTable[static_cast<sal_uInt8>(c)]; + } +}; + +} + +void Convention_A1::MakeColStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCCOL nCol ) +{ + if ( !rLimits.ValidCol(nCol) ) + rBuffer.append(ScResId(STR_NO_REF_TABLE)); + else + ::ScColToAlpha( rBuffer, nCol); +} + +void Convention_A1::MakeRowStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCROW nRow ) +{ + if ( !rLimits.ValidRow(nRow) ) + rBuffer.append(ScResId(STR_NO_REF_TABLE)); + else + rBuffer.append(sal_Int32(nRow + 1)); +} + +namespace { + +struct ConventionOOO_A1 : public Convention_A1 +{ + ConventionOOO_A1() : Convention_A1 (FormulaGrammar::CONV_OOO) { } + explicit ConventionOOO_A1( FormulaGrammar::AddressConvention eConv ) : Convention_A1 (eConv) { } + + static void MakeTabStr( OUStringBuffer &rBuf, const std::vector<OUString>& rTabNames, SCTAB nTab ) + { + if (o3tl::make_unsigned(nTab) >= rTabNames.size()) + rBuf.append(ScResId(STR_NO_REF_TABLE)); + else + rBuf.append(rTabNames[nTab]); + rBuf.append('.'); + } + + enum SingletonDisplay + { + SINGLETON_NONE, + SINGLETON_COL, + SINGLETON_ROW + }; + + static void MakeOneRefStrImpl( + const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, + std::u16string_view rErrRef, const std::vector<OUString>& rTabNames, + const ScSingleRefData& rRef, const ScAddress& rAbsRef, + bool bForceTab, bool bODF, SingletonDisplay eSingletonDisplay ) + { + if( rRef.IsFlag3D() || bForceTab ) + { + if (!ValidTab(rAbsRef.Tab()) || rRef.IsTabDeleted()) + { + if (!rRef.IsTabRel()) + rBuffer.append('$'); + rBuffer.append(rErrRef); + rBuffer.append('.'); + } + else + { + if (!rRef.IsTabRel()) + rBuffer.append('$'); + MakeTabStr(rBuffer, rTabNames, rAbsRef.Tab()); + } + } + else if (bODF) + rBuffer.append('.'); + + if (eSingletonDisplay != SINGLETON_ROW) + { + if (!rRef.IsColRel()) + rBuffer.append('$'); + if (!rLimits.ValidCol(rAbsRef.Col()) || rRef.IsColDeleted()) + rBuffer.append(rErrRef); + else + MakeColStr(rLimits, rBuffer, rAbsRef.Col()); + } + + if (eSingletonDisplay != SINGLETON_COL) + { + if (!rRef.IsRowRel()) + rBuffer.append('$'); + if (!rLimits.ValidRow(rAbsRef.Row()) || rRef.IsRowDeleted()) + rBuffer.append(rErrRef); + else + MakeRowStr(rLimits, rBuffer, rAbsRef.Row()); + } + } + + static SingletonDisplay getSingletonDisplay( const ScSheetLimits& rLimits, const ScAddress& rAbs1, const ScAddress& rAbs2, + const ScComplexRefData& rRef, bool bFromRangeName ) + { + // If any part is error, display as such. + if (!rLimits.ValidCol(rAbs1.Col()) || rRef.Ref1.IsColDeleted() || !rLimits.ValidRow(rAbs1.Row()) || rRef.Ref1.IsRowDeleted() || + !rLimits.ValidCol(rAbs2.Col()) || rRef.Ref2.IsColDeleted() || !rLimits.ValidRow(rAbs2.Row()) || rRef.Ref2.IsRowDeleted()) + return SINGLETON_NONE; + + // A:A or $A:$A or A:$A or $A:A + if (rRef.IsEntireCol(rLimits)) + return SINGLETON_COL; + + // Same if not in named expression and both rows of entire columns are + // relative references. + if (!bFromRangeName && rAbs1.Row() == 0 && rAbs2.Row() == rLimits.mnMaxRow && + rRef.Ref1.IsRowRel() && rRef.Ref2.IsRowRel()) + return SINGLETON_COL; + + // 1:1 or $1:$1 or 1:$1 or $1:1 + if (rRef.IsEntireRow(rLimits)) + return SINGLETON_ROW; + + // Same if not in named expression and both columns of entire rows are + // relative references. + if (!bFromRangeName && rAbs1.Col() == 0 && rAbs2.Col() == rLimits.mnMaxCol && + rRef.Ref1.IsColRel() && rRef.Ref2.IsColRel()) + return SINGLETON_ROW; + + return SINGLETON_NONE; + } + + virtual void makeRefStr( + ScSheetLimits& rLimits, + OUStringBuffer& rBuffer, + formula::FormulaGrammar::Grammar /*eGram*/, + const ScAddress& rPos, + const OUString& rErrRef, const std::vector<OUString>& rTabNames, + const ScComplexRefData& rRef, + bool bSingleRef, + bool bFromRangeName ) const override + { + // In case absolute/relative positions weren't separately available: + // transform relative to absolute! + ScAddress aAbs1 = rRef.Ref1.toAbs(rLimits, rPos), aAbs2; + if( !bSingleRef ) + aAbs2 = rRef.Ref2.toAbs(rLimits, rPos); + + SingletonDisplay eSingleton = bSingleRef ? SINGLETON_NONE : + getSingletonDisplay( rLimits, aAbs1, aAbs2, rRef, bFromRangeName); + MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref1, aAbs1, false, false, eSingleton); + if (!bSingleRef) + { + rBuffer.append(':'); + MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref2, aAbs2, aAbs1.Tab() != aAbs2.Tab(), false, + eSingleton); + } + } + + virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const override + { + switch (eSymType) + { + case ScCompiler::Convention::ABS_SHEET_PREFIX: + return '$'; + case ScCompiler::Convention::SHEET_SEPARATOR: + return '.'; + } + + return u'\0'; + } + + virtual bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName, + const ScDocument& rDoc, + const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks ) const override + { + return lcl_parseExternalName(rSymbol, rFile, rName, '#', rDoc, pExternalLinks); + } + + virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile, + const OUString& rName ) const override + { + return lcl_makeExternalNameStr( rFile, rName, '#', false); + } + + static bool makeExternalSingleRefStr( + const ScSheetLimits& rLimits, + OUStringBuffer& rBuffer, const OUString& rFileName, const OUString& rTabName, + const ScSingleRefData& rRef, const ScAddress& rPos, bool bDisplayTabName, bool bEncodeUrl ) + { + ScAddress aAbsRef = rRef.toAbs(rLimits, rPos); + if (bDisplayTabName) + { + OUString aFile; + if (bEncodeUrl) + aFile = rFileName; + else + aFile = INetURLObject::decode(rFileName, INetURLObject::DecodeMechanism::Unambiguous); + + rBuffer.append("'" + aFile.replaceAll("'", "''") + "'#"); + + if (!rRef.IsTabRel()) + rBuffer.append('$'); + ScRangeStringConverter::AppendTableName(rBuffer, rTabName); + + rBuffer.append('.'); + } + + if (!rRef.IsColRel()) + rBuffer.append('$'); + MakeColStr( rLimits, rBuffer, aAbsRef.Col()); + if (!rRef.IsRowRel()) + rBuffer.append('$'); + MakeRowStr( rLimits, rBuffer, aAbsRef.Row()); + + return true; + } + + static void makeExternalRefStrImpl( + const ScSheetLimits& rLimits, + OUStringBuffer& rBuffer, const ScAddress& rPos, const OUString& rFileName, + const OUString& rTabName, const ScSingleRefData& rRef, bool bODF ) + { + if (bODF) + rBuffer.append( '['); + + bool bEncodeUrl = bODF; + makeExternalSingleRefStr(rLimits, rBuffer, rFileName, rTabName, rRef, rPos, true, bEncodeUrl); + if (bODF) + rBuffer.append( ']'); + } + + virtual void makeExternalRefStr( + ScSheetLimits& rLimits, + OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName, + const OUString& rTabName, const ScSingleRefData& rRef ) const override + { + makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabName, rRef, false); + } + + static void makeExternalRefStrImpl( + const ScSheetLimits& rLimits, + OUStringBuffer& rBuffer, const ScAddress& rPos, const OUString& rFileName, + const std::vector<OUString>& rTabNames, const OUString& rTabName, + const ScComplexRefData& rRef, bool bODF ) + { + ScRange aAbsRange = rRef.toAbs(rLimits, rPos); + + if (bODF) + rBuffer.append( '['); + // Ensure that there's always a closing bracket, no premature returns. + bool bEncodeUrl = bODF; + + do + { + if (!makeExternalSingleRefStr(rLimits, rBuffer, rFileName, rTabName, rRef.Ref1, rPos, true, bEncodeUrl)) + break; + + rBuffer.append(':'); + + OUString aLastTabName; + bool bDisplayTabName = (aAbsRange.aStart.Tab() != aAbsRange.aEnd.Tab()); + if (bDisplayTabName) + { + // Get the name of the last table. + if (!lcl_getLastTabName(aLastTabName, rTabName, rTabNames, aAbsRange)) + { + OSL_FAIL( "ConventionOOO_A1::makeExternalRefStrImpl: sheet name not found"); + // aLastTabName contains #REF!, proceed. + } + } + else if (bODF) + rBuffer.append( '.'); // need at least the sheet separator in ODF + makeExternalSingleRefStr(rLimits, + rBuffer, rFileName, aLastTabName, rRef.Ref2, rPos, bDisplayTabName, bEncodeUrl); + } while (false); + + if (bODF) + rBuffer.append( ']'); + } + + virtual void makeExternalRefStr( + ScSheetLimits& rLimits, + OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName, + const std::vector<OUString>& rTabNames, const OUString& rTabName, + const ScComplexRefData& rRef ) const override + { + makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabNames, rTabName, rRef, false); + } +}; + +struct ConventionOOO_A1_ODF : public ConventionOOO_A1 +{ + ConventionOOO_A1_ODF() : ConventionOOO_A1 (FormulaGrammar::CONV_ODF) { } + + virtual void makeRefStr( + ScSheetLimits& rLimits, + OUStringBuffer& rBuffer, + formula::FormulaGrammar::Grammar eGram, + const ScAddress& rPos, + const OUString& rErrRef, const std::vector<OUString>& rTabNames, + const ScComplexRefData& rRef, + bool bSingleRef, + bool bFromRangeName ) const override + { + rBuffer.append('['); + // In case absolute/relative positions weren't separately available: + // transform relative to absolute! + ScAddress aAbs1 = rRef.Ref1.toAbs(rLimits, rPos), aAbs2; + if( !bSingleRef ) + aAbs2 = rRef.Ref2.toAbs(rLimits, rPos); + + if (FormulaGrammar::isODFF(eGram) && (rRef.Ref1.IsDeleted() || !rLimits.ValidAddress(aAbs1) || + (!bSingleRef && (rRef.Ref2.IsDeleted() || !rLimits.ValidAddress(aAbs2))))) + { + rBuffer.append(rErrRef); + // For ODFF write [#REF!], but not for PODF so apps reading ODF + // 1.0/1.1 may have a better chance if they implemented the old + // form. + } + else + { + SingletonDisplay eSingleton = bSingleRef ? SINGLETON_NONE : + getSingletonDisplay( rLimits, aAbs1, aAbs2, rRef, bFromRangeName); + MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref1, aAbs1, false, true, eSingleton); + if (!bSingleRef) + { + rBuffer.append(':'); + MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref2, aAbs2, aAbs1.Tab() != aAbs2.Tab(), true, + eSingleton); + } + } + rBuffer.append(']'); + } + + virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile, + const OUString& rName ) const override + { + return lcl_makeExternalNameStr( rFile, rName, '#', true); + } + + virtual void makeExternalRefStr( + ScSheetLimits& rLimits, + OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName, + const OUString& rTabName, const ScSingleRefData& rRef ) const override + { + makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabName, rRef, true); + } + + virtual void makeExternalRefStr( + ScSheetLimits& rLimits, + OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName, + const std::vector<OUString>& rTabNames, + const OUString& rTabName, const ScComplexRefData& rRef ) const override + { + makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabNames, rTabName, rRef, true); + } +}; + +struct ConventionXL +{ + virtual ~ConventionXL() + { + } + + static void GetTab( + const ScSheetLimits& rLimits, + const ScAddress& rPos, const std::vector<OUString>& rTabNames, + const ScSingleRefData& rRef, OUString& rTabName ) + { + ScAddress aAbs = rRef.toAbs(rLimits, rPos); + if (rRef.IsTabDeleted() || o3tl::make_unsigned(aAbs.Tab()) >= rTabNames.size()) + { + rTabName = ScResId( STR_NO_REF_TABLE ); + return; + } + rTabName = rTabNames[aAbs.Tab()]; + } + + static void MakeTabStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuf, + const ScAddress& rPos, + const std::vector<OUString>& rTabNames, + const ScComplexRefData& rRef, + bool bSingleRef ) + { + if( !rRef.Ref1.IsFlag3D() ) + return; + + OUString aStartTabName, aEndTabName; + + GetTab(rLimits, rPos, rTabNames, rRef.Ref1, aStartTabName); + + if( !bSingleRef && rRef.Ref2.IsFlag3D() ) + { + GetTab(rLimits, rPos, rTabNames, rRef.Ref2, aEndTabName); + } + + rBuf.append( aStartTabName ); + if( !bSingleRef && rRef.Ref2.IsFlag3D() && aStartTabName != aEndTabName ) + { + rBuf.append( ':' ); + rBuf.append( aEndTabName ); + } + + rBuf.append( '!' ); + } + + static sal_Unicode getSpecialSymbol( ScCompiler::Convention::SpecialSymbolType eSymType ) + { + switch (eSymType) + { + case ScCompiler::Convention::ABS_SHEET_PREFIX: + return u'\0'; + case ScCompiler::Convention::SHEET_SEPARATOR: + return '!'; + } + return u'\0'; + } + + static bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName, + const ScDocument& rDoc, + const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks ) + { + return lcl_parseExternalName( rSymbol, rFile, rName, '!', rDoc, pExternalLinks); + } + + static OUString makeExternalNameStr( const OUString& rFile, const OUString& rName ) + { + return lcl_makeExternalNameStr( rFile, rName, '!', false); + } + + static void makeExternalDocStr( OUStringBuffer& rBuffer, std::u16string_view rFullName ) + { + // Format that is easier to deal with inside OOo, because we use file + // URL, and all characters are allowed. Check if it makes sense to do + // it the way Gnumeric does it. Gnumeric doesn't use the URL form + // and allows relative file path. + // + // ['file:///path/to/source/filename.xls'] + + rBuffer.append('['); + rBuffer.append('\''); + OUString aFullName = INetURLObject::decode(rFullName, INetURLObject::DecodeMechanism::Unambiguous); + + const sal_Unicode* pBuf = aFullName.getStr(); + sal_Int32 nLen = aFullName.getLength(); + for (sal_Int32 i = 0; i < nLen; ++i) + { + const sal_Unicode c = pBuf[i]; + if (c == '\'') + rBuffer.append(c); + rBuffer.append(c); + } + rBuffer.append('\''); + rBuffer.append(']'); + } + + static void makeExternalTabNameRange( OUStringBuffer& rBuf, const OUString& rTabName, + const vector<OUString>& rTabNames, + const ScRange& rRef ) + { + OUString aLastTabName; + if (!lcl_getLastTabName(aLastTabName, rTabName, rTabNames, rRef)) + { + ScRangeStringConverter::AppendTableName(rBuf, aLastTabName); + return; + } + + ScRangeStringConverter::AppendTableName(rBuf, rTabName); + if (rTabName != aLastTabName) + { + rBuf.append(':'); + ScRangeStringConverter::AppendTableName(rBuf, aLastTabName); + } + } + + virtual void parseExternalDocName( const OUString& rFormula, sal_Int32& rSrcPos ) const + { + sal_Int32 nLen = rFormula.getLength(); + const sal_Unicode* p = rFormula.getStr(); + sal_Unicode cPrev = 0; + for (sal_Int32 i = rSrcPos; i < nLen; ++i) + { + sal_Unicode c = p[i]; + if (i == rSrcPos) + { + // first character must be '['. + if (c != '[') + return; + } + else if (i == rSrcPos + 1) + { + // second character must be a single quote. + if (c != '\'') + return; + } + else if (c == '\'') + { + if (cPrev == '\'') + // two successive single quote is treated as a single + // valid character. + c = 'a'; + } + else if (c == ']') + { + if (cPrev == '\'') + { + // valid source document path found. Increment the + // current position to skip the source path. + rSrcPos = i + 1; + if (rSrcPos >= nLen) + rSrcPos = nLen - 1; + return; + } + else + return; + } + else + { + // any other character + if (i > rSrcPos + 2 && cPrev == '\'') + // unless it's the 3rd character, a normal character + // following immediately a single quote is invalid. + return; + } + cPrev = c; + } + } +}; + +struct ConventionXL_A1 : public Convention_A1, public ConventionXL +{ + ConventionXL_A1() : Convention_A1( FormulaGrammar::CONV_XL_A1 ) { } + explicit ConventionXL_A1( FormulaGrammar::AddressConvention eConv ) : Convention_A1( eConv ) { } + + static void makeSingleCellStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuf, const ScSingleRefData& rRef, const ScAddress& rAbs ) + { + if (!rRef.IsColRel()) + rBuf.append('$'); + MakeColStr(rLimits, rBuf, rAbs.Col()); + if (!rRef.IsRowRel()) + rBuf.append('$'); + MakeRowStr(rLimits, rBuf, rAbs.Row()); + } + + virtual void makeRefStr( + ScSheetLimits& rLimits, + OUStringBuffer& rBuf, + formula::FormulaGrammar::Grammar /*eGram*/, + const ScAddress& rPos, + const OUString& rErrRef, const std::vector<OUString>& rTabNames, + const ScComplexRefData& rRef, + bool bSingleRef, + bool /*bFromRangeName*/ ) const override + { + ScComplexRefData aRef( rRef ); + + // Play fast and loose with invalid refs. There is not much point in producing + // Foo!A1:#REF! versus #REF! at this point + ScAddress aAbs1 = aRef.Ref1.toAbs(rLimits, rPos), aAbs2; + + MakeTabStr(rLimits, rBuf, rPos, rTabNames, aRef, bSingleRef); + + if (!rLimits.ValidAddress(aAbs1)) + { + rBuf.append(rErrRef); + return; + } + + if( !bSingleRef ) + { + aAbs2 = aRef.Ref2.toAbs(rLimits, rPos); + if (!rLimits.ValidAddress(aAbs2)) + { + rBuf.append(rErrRef); + return; + } + + if (aAbs1.Col() == 0 && aAbs2.Col() >= rLimits.mnMaxCol) + { + if (!aRef.Ref1.IsRowRel()) + rBuf.append( '$' ); + MakeRowStr(rLimits, rBuf, aAbs1.Row()); + rBuf.append( ':' ); + if (!aRef.Ref2.IsRowRel()) + rBuf.append( '$' ); + MakeRowStr(rLimits, rBuf, aAbs2.Row()); + return; + } + + if (aAbs1.Row() == 0 && aAbs2.Row() >= rLimits.mnMaxRow) + { + if (!aRef.Ref1.IsColRel()) + rBuf.append( '$' ); + MakeColStr(rLimits, rBuf, aAbs1.Col()); + rBuf.append( ':' ); + if (!aRef.Ref2.IsColRel()) + rBuf.append( '$' ); + MakeColStr(rLimits, rBuf, aAbs2.Col()); + return; + } + } + + makeSingleCellStr(rLimits, rBuf, aRef.Ref1, aAbs1); + if (!bSingleRef) + { + rBuf.append( ':' ); + makeSingleCellStr(rLimits, rBuf, aRef.Ref2, aAbs2); + } + } + + virtual ParseResult parseAnyToken( const OUString& rFormula, + sal_Int32 nSrcPos, + const CharClass* pCharClass, + bool bGroupSeparator) const override + { + parseExternalDocName(rFormula, nSrcPos); + + ParseResult aRet; + if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) ) + return aRet; + + constexpr sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER | + KParseTokens::ASC_UNDERSCORE | KParseTokens::ASC_DOLLAR; + constexpr sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT; + // '?' allowed in range names + static constexpr OUString aAddAllowed(u"?!"_ustr); + return pCharClass->parseAnyToken( rFormula, + nSrcPos, nStartFlags, aAddAllowed, + (bGroupSeparator ? nContFlags | KParseTokens::GROUP_SEPARATOR_IN_NUMBER : nContFlags), + aAddAllowed ); + } + + virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const override + { + return ConventionXL::getSpecialSymbol(eSymType); + } + + virtual bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName, + const ScDocument& rDoc, + const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks ) const override + { + return ConventionXL::parseExternalName( rSymbol, rFile, rName, rDoc, pExternalLinks); + } + + virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile, + const OUString& rName ) const override + { + return ConventionXL::makeExternalNameStr(rFile, rName); + } + + virtual void makeExternalRefStr( + ScSheetLimits& rLimits, + OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName, + const OUString& rTabName, const ScSingleRefData& rRef ) const override + { + // ['file:///path/to/file/filename.xls']'Sheet Name'!$A$1 + // This is a little different from the format Excel uses, as Excel + // puts [] only around the file name. But we need to enclose the + // whole file path with [] because the file name can contain any + // characters. + + ConventionXL::makeExternalDocStr(rBuffer, rFileName); + ScRangeStringConverter::AppendTableName(rBuffer, rTabName); + rBuffer.append('!'); + + makeSingleCellStr(rLimits, rBuffer, rRef, rRef.toAbs(rLimits, rPos)); + } + + virtual void makeExternalRefStr( + ScSheetLimits& rLimits, + OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName, + const std::vector<OUString>& rTabNames, const OUString& rTabName, + const ScComplexRefData& rRef ) const override + { + ScRange aAbsRef = rRef.toAbs(rLimits, rPos); + + ConventionXL::makeExternalDocStr(rBuffer, rFileName); + ConventionXL::makeExternalTabNameRange(rBuffer, rTabName, rTabNames, aAbsRef); + rBuffer.append('!'); + + makeSingleCellStr(rLimits, rBuffer, rRef.Ref1, aAbsRef.aStart); + if (aAbsRef.aStart != aAbsRef.aEnd) + { + rBuffer.append(':'); + makeSingleCellStr(rLimits, rBuffer, rRef.Ref2, aAbsRef.aEnd); + } + } +}; + +struct ConventionXL_OOX : public ConventionXL_A1 +{ + ConventionXL_OOX() : ConventionXL_A1( FormulaGrammar::CONV_XL_OOX ) { } + + virtual void makeRefStr( ScSheetLimits& rLimits, + OUStringBuffer& rBuf, + formula::FormulaGrammar::Grammar eGram, + const ScAddress& rPos, + const OUString& rErrRef, const std::vector<OUString>& rTabNames, + const ScComplexRefData& rRef, + bool bSingleRef, + bool bFromRangeName ) const override + { + // In OOXML relative references in named expressions are relative to + // column 0 and row 0. Relative sheet references don't exist. + ScAddress aPos( rPos ); + if (bFromRangeName) + { + // XXX NOTE: by decrementing the reference position we may end up + // with resolved references with negative values. There's no proper + // way to solve that or wrap them around without sheet dimensions + // that are stored along. That, or blindly assume fixed dimensions + // here and in import. + /* TODO: maybe do that blind fixed dimensions wrap? */ + aPos.SetCol(0); + aPos.SetRow(0); + } + + if (rRef.Ref1.IsDeleted() || (!bSingleRef && rRef.Ref2.IsDeleted())) + { + // For OOXML write plain "#REF!" instead of detailed sheet/col/row + // information. + rBuf.append(rErrRef); + return; + } + + { + ScAddress aAbs1 = rRef.Ref1.toAbs(rLimits, rPos); + if (!rLimits.ValidAddress(aAbs1) + || o3tl::make_unsigned(aAbs1.Tab()) >= rTabNames.size()) + { + rBuf.append(rErrRef); + return; + } + } + + if (!bSingleRef) + { + ScAddress aAbs2 = rRef.Ref2.toAbs(rLimits, rPos); + if (!rLimits.ValidAddress(aAbs2) + || o3tl::make_unsigned(aAbs2.Tab()) >= rTabNames.size()) + { + rBuf.append(rErrRef); + return; + } + } + + ConventionXL_A1::makeRefStr( rLimits, rBuf, eGram, aPos, rErrRef, rTabNames, rRef, bSingleRef, bFromRangeName); + } + + virtual OUString makeExternalNameStr( sal_uInt16 nFileId, const OUString& /*rFile*/, + const OUString& rName ) const override + { + // [N]!DefinedName is a workbook global name. + return OUString( "[" + OUString::number(nFileId+1) + "]!" + rName ); + + /* TODO: add support for sheet local names, would be + * [N]'Sheet Name'!DefinedName + * Similar to makeExternalRefStr() but with DefinedName instead of + * CellStr. */ + } + + virtual void parseExternalDocName(const OUString& rFormula, sal_Int32& rSrcPos) const override + { + sal_Int32 nLen = rFormula.getLength(); + const sal_Unicode* p = rFormula.getStr(); + for (sal_Int32 i = rSrcPos; i < nLen; ++i) + { + sal_Unicode c = p[i]; + if (i == rSrcPos) + { + // first character must be '['. + if (c != '[') + return; + } + else if (c == ']') + { + rSrcPos = i + 1; + return; + } + } + } + + virtual void makeExternalRefStr( + ScSheetLimits& rLimits, + OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 nFileId, const OUString& /*rFileName*/, + const OUString& rTabName, const ScSingleRefData& rRef ) const override + { + // '[N]Sheet Name'!$A$1 or [N]SheetName!$A$1 + // Where N is a 1-based positive integer number of a file name in OOXML + // xl/externalLinks/externalLinkN.xml + + OUString aQuotedTab( rTabName); + ScCompiler::CheckTabQuotes( aQuotedTab); + if (!aQuotedTab.isEmpty() && aQuotedTab[0] == '\'') + { + rBuffer.append('\''); + ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId); + rBuffer.append( aQuotedTab.subView(1)); + } + else + { + ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId); + rBuffer.append( aQuotedTab); + } + rBuffer.append('!'); + + makeSingleCellStr(rLimits, rBuffer, rRef, rRef.toAbs(rLimits, rPos)); + } + + virtual void makeExternalRefStr( + ScSheetLimits& rLimits, + OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 nFileId, const OUString& /*rFileName*/, + const std::vector<OUString>& rTabNames, const OUString& rTabName, + const ScComplexRefData& rRef ) const override + { + // '[N]Sheet One':'Sheet Two'!A1:B2 or [N]SheetOne!A1:B2 + // Actually Excel writes '[N]Sheet One:Sheet Two'!A1:B2 but reads the + // simpler to produce and more logical form with independently quoted + // sheet names as well. The [N] having to be within the quoted sheet + // name is ugly enough... + + ScRange aAbsRef = rRef.toAbs(rLimits, rPos); + + OUStringBuffer aBuf; + ConventionXL::makeExternalTabNameRange( aBuf, rTabName, rTabNames, aAbsRef); + if (!aBuf.isEmpty() && aBuf[0] == '\'') + { + rBuffer.append('\''); + ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId); + rBuffer.append( aBuf.subView(1)); + } + else + { + ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId); + rBuffer.append( aBuf); + } + rBuffer.append('!'); + + makeSingleCellStr(rLimits, rBuffer, rRef.Ref1, aAbsRef.aStart); + if (aAbsRef.aStart != aAbsRef.aEnd) + { + rBuffer.append(':'); + makeSingleCellStr(rLimits, rBuffer, rRef.Ref2, aAbsRef.aEnd); + } + } + + static void makeExternalDocStr( OUStringBuffer& rBuffer, sal_uInt16 nFileId ) + { + rBuffer.append("[" + OUString::number( static_cast<sal_Int32>(nFileId+1) ) + "]"); + } +}; + +} + +static void +r1c1_add_col( OUStringBuffer &rBuf, const ScSingleRefData& rRef, const ScAddress& rAbsRef ) +{ + rBuf.append( 'C' ); + if( rRef.IsColRel() ) + { + SCCOL nCol = rRef.Col(); + if (nCol != 0) + rBuf.append("[" + OUString::number(nCol) + "]"); + } + else + rBuf.append( static_cast<sal_Int32>(rAbsRef.Col() + 1) ); +} +static void +r1c1_add_row( OUStringBuffer &rBuf, const ScSingleRefData& rRef, const ScAddress& rAbsRef ) +{ + rBuf.append( 'R' ); + if( rRef.IsRowRel() ) + { + if (rRef.Row() != 0) + { + rBuf.append("[" + OUString::number(rRef.Row()) + "]"); + } + } + else + rBuf.append( rAbsRef.Row() + 1 ); +} + +namespace { + +struct ConventionXL_R1C1 : public ScCompiler::Convention, public ConventionXL +{ + ConventionXL_R1C1() : ScCompiler::Convention( FormulaGrammar::CONV_XL_R1C1 ) { } + + virtual void makeRefStr( ScSheetLimits& rLimits, + OUStringBuffer& rBuf, + formula::FormulaGrammar::Grammar /*eGram*/, + const ScAddress& rPos, + const OUString& rErrRef, const std::vector<OUString>& rTabNames, + const ScComplexRefData& rRef, + bool bSingleRef, + bool /*bFromRangeName*/ ) const override + { + ScRange aAbsRef = rRef.toAbs(rLimits, rPos); + ScComplexRefData aRef( rRef ); + + MakeTabStr(rLimits, rBuf, rPos, rTabNames, aRef, bSingleRef); + + // Play fast and loose with invalid refs. There is not much point in producing + // Foo!A1:#REF! versus #REF! at this point + if (!rLimits.ValidCol(aAbsRef.aStart.Col()) || !rLimits.ValidRow(aAbsRef.aStart.Row())) + { + rBuf.append(rErrRef); + return; + } + + if( !bSingleRef ) + { + if (!rLimits.ValidCol(aAbsRef.aEnd.Col()) || !rLimits.ValidRow(aAbsRef.aEnd.Row())) + { + rBuf.append(rErrRef); + return; + } + + if (aAbsRef.aStart.Col() == 0 && aAbsRef.aEnd.Col() >= rLimits.mnMaxCol) + { + r1c1_add_row(rBuf, rRef.Ref1, aAbsRef.aStart); + if (aAbsRef.aStart.Row() != aAbsRef.aEnd.Row() || + rRef.Ref1.IsRowRel() != rRef.Ref2.IsRowRel() ) + { + rBuf.append( ':' ); + r1c1_add_row(rBuf, rRef.Ref2, aAbsRef.aEnd); + } + return; + + } + + if (aAbsRef.aStart.Row() == 0 && aAbsRef.aEnd.Row() >= rLimits.mnMaxRow) + { + r1c1_add_col(rBuf, rRef.Ref1, aAbsRef.aStart); + if (aAbsRef.aStart.Col() != aAbsRef.aEnd.Col() || + rRef.Ref1.IsColRel() != rRef.Ref2.IsColRel()) + { + rBuf.append( ':' ); + r1c1_add_col(rBuf, rRef.Ref2, aAbsRef.aEnd); + } + return; + } + } + + r1c1_add_row(rBuf, rRef.Ref1, aAbsRef.aStart); + r1c1_add_col(rBuf, rRef.Ref1, aAbsRef.aStart); + if (!bSingleRef) + { + rBuf.append( ':' ); + r1c1_add_row(rBuf, rRef.Ref2, aAbsRef.aEnd); + r1c1_add_col(rBuf, rRef.Ref2, aAbsRef.aEnd); + } + } + + ParseResult parseAnyToken( const OUString& rFormula, + sal_Int32 nSrcPos, + const CharClass* pCharClass, + bool bGroupSeparator) const override + { + parseExternalDocName(rFormula, nSrcPos); + + ParseResult aRet; + if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) ) + return aRet; + + constexpr sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER | + KParseTokens::ASC_UNDERSCORE ; + constexpr sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT; + // '?' allowed in range names + static constexpr OUString aAddAllowed(u"?-[]!"_ustr); + + return pCharClass->parseAnyToken( rFormula, + nSrcPos, nStartFlags, aAddAllowed, + (bGroupSeparator ? nContFlags | KParseTokens::GROUP_SEPARATOR_IN_NUMBER : nContFlags), + aAddAllowed ); + } + + virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const override + { + return ConventionXL::getSpecialSymbol(eSymType); + } + + virtual bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName, + const ScDocument& rDoc, + const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks ) const override + { + return ConventionXL::parseExternalName( rSymbol, rFile, rName, rDoc, pExternalLinks); + } + + virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile, + const OUString& rName ) const override + { + return ConventionXL::makeExternalNameStr(rFile, rName); + } + + virtual void makeExternalRefStr( + ScSheetLimits& rLimits, + OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName, + const OUString& rTabName, const ScSingleRefData& rRef ) const override + { + // ['file:///path/to/file/filename.xls']'Sheet Name'!$A$1 + // This is a little different from the format Excel uses, as Excel + // puts [] only around the file name. But we need to enclose the + // whole file path with [] because the file name can contain any + // characters. + + ScAddress aAbsRef = rRef.toAbs(rLimits, rPos); + ConventionXL::makeExternalDocStr(rBuffer, rFileName); + ScRangeStringConverter::AppendTableName(rBuffer, rTabName); + rBuffer.append('!'); + + r1c1_add_row(rBuffer, rRef, aAbsRef); + r1c1_add_col(rBuffer, rRef, aAbsRef); + } + + virtual void makeExternalRefStr( + ScSheetLimits& rLimits, + OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName, + const std::vector<OUString>& rTabNames, const OUString& rTabName, + const ScComplexRefData& rRef ) const override + { + ScRange aAbsRef = rRef.toAbs(rLimits, rPos); + + ConventionXL::makeExternalDocStr(rBuffer, rFileName); + ConventionXL::makeExternalTabNameRange(rBuffer, rTabName, rTabNames, aAbsRef); + rBuffer.append('!'); + + if (!rLimits.ValidCol(aAbsRef.aEnd.Col()) || !rLimits.ValidRow(aAbsRef.aEnd.Row())) + { + rBuffer.append(ScResId(STR_NO_REF_TABLE)); + return; + } + + if (aAbsRef.aStart.Col() == 0 && aAbsRef.aEnd.Col() >= rLimits.mnMaxCol) + { + r1c1_add_row(rBuffer, rRef.Ref1, aAbsRef.aStart); + if (aAbsRef.aStart.Row() != aAbsRef.aEnd.Row() || rRef.Ref1.IsRowRel() != rRef.Ref2.IsRowRel()) + { + rBuffer.append(':'); + r1c1_add_row(rBuffer, rRef.Ref2, aAbsRef.aEnd); + } + return; + } + + if (aAbsRef.aStart.Row() == 0 && aAbsRef.aEnd.Row() >= rLimits.mnMaxRow) + { + r1c1_add_col(rBuffer, rRef.Ref1, aAbsRef.aStart); + if (aAbsRef.aStart.Col() != aAbsRef.aEnd.Col() || rRef.Ref1.IsColRel() != rRef.Ref2.IsColRel()) + { + rBuffer.append(':'); + r1c1_add_col(rBuffer, rRef.Ref2, aAbsRef.aEnd); + } + return; + } + + r1c1_add_row(rBuffer, rRef.Ref1, aAbsRef.aStart); + r1c1_add_col(rBuffer, rRef.Ref1, aAbsRef.aStart); + rBuffer.append(':'); + r1c1_add_row(rBuffer, rRef.Ref2, aAbsRef.aEnd); + r1c1_add_col(rBuffer, rRef.Ref2, aAbsRef.aEnd); + } + + virtual ScCharFlags getCharTableFlags( sal_Unicode c, sal_Unicode cLast ) const override + { + ScCharFlags nFlags = mpCharTable[static_cast<sal_uInt8>(c)]; + if (c == '-' && cLast == '[') + // '-' can occur within a reference string only after '[' e.g. R[-1]C. + nFlags |= ScCharFlags::Ident; + return nFlags; + } +}; + +} + +ScCompiler::ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos, ScTokenArray& rArr, + bool bComputeII, bool bMatrixFlag, const ScInterpreterContext* pContext ) + : FormulaCompiler(rArr, bComputeII, bMatrixFlag), + rDoc(rCxt.getDoc()), + aPos(rPos), + mpFormatter(pContext ? pContext->GetFormatTable() : rDoc.GetFormatTable()), + mpInterpreterContext(pContext), + mnCurrentSheetTab(-1), + mnCurrentSheetEndPos(0), + pCharClass(&ScGlobal::getCharClass()), + mbCharClassesDiffer(false), + mnPredetectedReference(0), + mnRangeOpPosInSymbol(-1), + pConv(GetRefConvention(FormulaGrammar::CONV_OOO)), + meExtendedErrorDetection(EXTENDED_ERROR_DETECTION_NONE), + mbCloseBrackets(true), + mbRewind(false), + mbRefConventionChartOOXML(false), + maTabNames(rCxt.getTabNames()) +{ + SetGrammar(rCxt.getGrammar()); +} + +ScCompiler::ScCompiler( ScDocument& rDocument, const ScAddress& rPos, ScTokenArray& rArr, + formula::FormulaGrammar::Grammar eGrammar, + bool bComputeII, bool bMatrixFlag, const ScInterpreterContext* pContext ) + : FormulaCompiler(rArr, bComputeII, bMatrixFlag), + rDoc( rDocument ), + aPos( rPos ), + mpFormatter(pContext ? pContext->GetFormatTable() : rDoc.GetFormatTable()), + mpInterpreterContext(pContext), + mnCurrentSheetTab(-1), + mnCurrentSheetEndPos(0), + nSrcPos(0), + pCharClass( &ScGlobal::getCharClass() ), + mbCharClassesDiffer(false), + mnPredetectedReference(0), + mnRangeOpPosInSymbol(-1), + pConv( GetRefConvention( FormulaGrammar::CONV_OOO ) ), + meExtendedErrorDetection( EXTENDED_ERROR_DETECTION_NONE ), + mbCloseBrackets( true ), + mbRewind( false ), + mbRefConventionChartOOXML( false ) +{ + SetGrammar( (eGrammar == formula::FormulaGrammar::GRAM_UNSPECIFIED) ? + rDocument.GetGrammar() : + eGrammar ); +} + +ScCompiler::ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos, + bool bComputeII, bool bMatrixFlag, const ScInterpreterContext* pContext ) + : FormulaCompiler(bComputeII, bMatrixFlag), + rDoc(rCxt.getDoc()), + aPos(rPos), + mpFormatter(pContext ? pContext->GetFormatTable() : rDoc.GetFormatTable()), + mpInterpreterContext(pContext), + mnCurrentSheetTab(-1), + mnCurrentSheetEndPos(0), + pCharClass(&ScGlobal::getCharClass()), + mbCharClassesDiffer(false), + mnPredetectedReference(0), + mnRangeOpPosInSymbol(-1), + pConv(GetRefConvention(FormulaGrammar::CONV_OOO)), + meExtendedErrorDetection(EXTENDED_ERROR_DETECTION_NONE), + mbCloseBrackets(true), + mbRewind(false), + mbRefConventionChartOOXML(false), + maTabNames(rCxt.getTabNames()) +{ + SetGrammar(rCxt.getGrammar()); +} + +ScCompiler::ScCompiler( ScDocument& rDocument, const ScAddress& rPos, + formula::FormulaGrammar::Grammar eGrammar, + bool bComputeII, bool bMatrixFlag, const ScInterpreterContext* pContext ) + : FormulaCompiler(bComputeII, bMatrixFlag), + rDoc( rDocument ), + aPos( rPos ), + mpFormatter(pContext ? pContext->GetFormatTable() : rDoc.GetFormatTable()), + mpInterpreterContext(pContext), + mnCurrentSheetTab(-1), + mnCurrentSheetEndPos(0), + nSrcPos(0), + pCharClass( &ScGlobal::getCharClass() ), + mbCharClassesDiffer(false), + mnPredetectedReference(0), + mnRangeOpPosInSymbol(-1), + pConv( GetRefConvention( FormulaGrammar::CONV_OOO ) ), + meExtendedErrorDetection( EXTENDED_ERROR_DETECTION_NONE ), + mbCloseBrackets( true ), + mbRewind( false ), + mbRefConventionChartOOXML( false ) +{ + SetGrammar( (eGrammar == formula::FormulaGrammar::GRAM_UNSPECIFIED) ? + rDocument.GetGrammar() : + eGrammar ); +} + +ScCompiler::~ScCompiler() +{ +} + +void ScCompiler::CheckTabQuotes( OUString& rString, + const FormulaGrammar::AddressConvention eConv ) +{ + sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER | KParseTokens::ASC_UNDERSCORE; + sal_Int32 nContFlags = nStartFlags; + ParseResult aRes = ScGlobal::getCharClass().parsePredefinedToken( + KParseType::IDENTNAME, rString, 0, nStartFlags, OUString(), nContFlags, OUString()); + bool bNeedsQuote = !((aRes.TokenType & KParseType::IDENTNAME) && aRes.EndPos == rString.getLength()); + + switch ( eConv ) + { + default : + case FormulaGrammar::CONV_UNSPECIFIED : + break; + case FormulaGrammar::CONV_OOO : + case FormulaGrammar::CONV_XL_A1 : + case FormulaGrammar::CONV_XL_R1C1 : + case FormulaGrammar::CONV_XL_OOX : + case FormulaGrammar::CONV_ODF : + if( bNeedsQuote ) + { + // escape embedded quotes + rString = rString.replaceAll( "'", "''" ); + } + break; + } + + if ( !bNeedsQuote && CharClass::isAsciiNumeric( rString ) ) + { + // Prevent any possible confusion resulting from pure numeric sheet names. + bNeedsQuote = true; + } + + if( bNeedsQuote ) + { + rString = "'" + rString + "'"; + } +} + +sal_Int32 ScCompiler::GetDocTabPos( const OUString& rString ) +{ + if (rString[0] != '\'') + return -1; + sal_Int32 nPos = ScGlobal::FindUnquoted( rString, SC_COMPILER_FILE_TAB_SEP); + // it must be 'Doc'# + if (nPos != -1 && rString[nPos-1] != '\'') + nPos = -1; + return nPos; +} + +void ScCompiler::SetRefConvention( FormulaGrammar::AddressConvention eConv ) +{ + const Convention* p = GetRefConvention(eConv); + if (p) + SetRefConvention(p); +} + +const ScCompiler::Convention* ScCompiler::GetRefConvention( FormulaGrammar::AddressConvention eConv ) +{ + + switch (eConv) + { + case FormulaGrammar::CONV_OOO: + { + static const ConventionOOO_A1 ConvOOO_A1; + return &ConvOOO_A1; + } + case FormulaGrammar::CONV_ODF: + { + static const ConventionOOO_A1_ODF ConvOOO_A1_ODF; + return &ConvOOO_A1_ODF; + } + case FormulaGrammar::CONV_XL_A1: + { + static const ConventionXL_A1 ConvXL_A1; + return &ConvXL_A1; + } + case FormulaGrammar::CONV_XL_R1C1: + { + static const ConventionXL_R1C1 ConvXL_R1C1; + return &ConvXL_R1C1; + } + case FormulaGrammar::CONV_XL_OOX: + { + static const ConventionXL_OOX ConvXL_OOX; + return &ConvXL_OOX; + } + case FormulaGrammar::CONV_UNSPECIFIED: + default: + ; + } + + return nullptr; +} + +void ScCompiler::SetRefConvention( const ScCompiler::Convention *pConvP ) +{ + pConv = pConvP; + meGrammar = FormulaGrammar::mergeToGrammar( meGrammar, pConv->meConv); + assert( FormulaGrammar::isSupported( meGrammar)); +} + +void ScCompiler::SetError(FormulaError nError) +{ + if( pArr->GetCodeError() == FormulaError::NONE) + pArr->SetCodeError( nError); +} + +static sal_Unicode* lcl_UnicodeStrNCpy( sal_Unicode* pDst, const sal_Unicode* pSrc, sal_Int32 nMax ) +{ + const sal_Unicode* const pStop = pDst + nMax; + while ( pDst < pStop ) + { + *pDst++ = *pSrc++; + } + *pDst = 0; + return pDst; +} + +// p1 MUST contain at least n characters, or terminate with NIL. +// p2 MUST pass upper case letters, if any. +// n MUST not be greater than length of p2 +static bool lcl_isUnicodeIgnoreAscii( const sal_Unicode* p1, const char* p2, size_t n ) +{ + for (size_t i=0; i<n; ++i) + { + if (!p1[i]) + return false; + if (p1[i] != p2[i]) + { + if (p1[i] < 'a' || 'z' < p1[i]) + return false; // not a lower case letter + if (p2[i] < 'A' || 'Z' < p2[i]) + return false; // not a letter to match + if (p1[i] != p2[i] + 0x20) + return false; // lower case doesn't match either + } + } + return true; +} + +// static +void ScCompiler::addWhitespace( std::vector<ScCompiler::Whitespace> & rvSpaces, + ScCompiler::Whitespace & rSpace, sal_Unicode c, sal_Int32 n ) +{ + if (rSpace.cChar != c) + { + if (rSpace.cChar && rSpace.nCount > 0) + rvSpaces.emplace_back(rSpace); + rSpace.reset(c); + } + rSpace.nCount += n; +} + +// NextSymbol + +// Parses the formula into separate symbols for further processing. +// XXX NOTE: this is a rough sketch of the original idea, there are other +// states that were added and didn't make it into this table and things are +// more complicated. Use the source, Luke. + +// initial state = GetChar + +// old state | read character | action | new state +//---------------+-------------------+-----------------------+--------------- +// GetChar | ;()+-*/^=& | Symbol=char | Stop +// | <> | Symbol=char | GetBool +// | $ letter | Symbol=char | GetWord +// | number | Symbol=char | GetValue +// | " | none | GetString +// | other | none | GetChar +//---------------+-------------------+-----------------------+--------------- +// GetBool | => | Symbol=Symbol+char | Stop +// | other | Dec(CharPos) | Stop +//---------------+-------------------+-----------------------+--------------- +// GetWord | SepSymbol | Dec(CharPos) | Stop +// | ()+-*/^=<>&~ | | +// | space | Dec(CharPos) | Stop +// | $_:. | | +// | letter, number | Symbol=Symbol+char | GetWord +// | other | error | Stop +//---------------+-------------------+-----------------------+--------------- +// GetValue | ;()*/^=<>& | | +// | space | Dec(CharPos) | Stop +// | number E+-%,. | Symbol=Symbol+char | GetValue +// | other | error | Stop +//---------------+-------------------+-----------------------+--------------- +// GetString | " | none | Stop +// | other | Symbol=Symbol+char | GetString +//---------------+-------------------+-----------------------+--------------- + +std::vector<ScCompiler::Whitespace> ScCompiler::NextSymbol(bool bInArray) +{ + std::vector<Whitespace> vSpaces; + cSymbol[MAXSTRLEN] = 0; // end + sal_Unicode* pSym = cSymbol; + const sal_Unicode* const pStart = aFormula.getStr(); + const sal_Unicode* pSrc = pStart + nSrcPos; + bool bi18n = false; + sal_Unicode c = *pSrc; + sal_Unicode cLast = 0; + bool bQuote = false; + mnRangeOpPosInSymbol = -1; + ScanState eState = ssGetChar; + Whitespace aSpace; + sal_Unicode cSep = mxSymbols->getSymbolChar( ocSep); + sal_Unicode cArrayColSep = mxSymbols->getSymbolChar( ocArrayColSep); + sal_Unicode cArrayRowSep = mxSymbols->getSymbolChar( ocArrayRowSep); + sal_Unicode cDecSep = (mxSymbols->isEnglishLocale() ? '.' : ScGlobal::getLocaleData().getNumDecimalSep()[0]); + sal_Unicode cDecSepAlt = (mxSymbols->isEnglishLocale() ? 0 : ScGlobal::getLocaleData().getNumDecimalSepAlt().toChar()); + + // special symbols specific to address convention used + sal_Unicode cSheetPrefix = pConv->getSpecialSymbol(ScCompiler::Convention::ABS_SHEET_PREFIX); + sal_Unicode cSheetSep = pConv->getSpecialSymbol(ScCompiler::Convention::SHEET_SEPARATOR); + + int nDecSeps = 0; + bool bAutoIntersection = false; + size_t nAutoIntersectionSpacesPos = 0; + int nRefInName = 0; + bool bErrorConstantHadSlash = false; + mnPredetectedReference = 0; + // try to parse simple tokens before calling i18n parser + while ((c != 0) && (eState != ssStop) ) + { + pSrc++; + ScCharFlags nMask = GetCharTableFlags( c, cLast ); + + // The parameter separator and the array column and row separators end + // things unconditionally if not in string or reference. + if (c == cSep || (bInArray && (c == cArrayColSep || c == cArrayRowSep))) + { + switch (eState) + { + // these are to be continued + case ssGetString: + case ssSkipString: + case ssGetReference: + case ssSkipReference: + case ssGetTableRefItem: + case ssGetTableRefColumn: + break; + default: + if (eState == ssGetChar) + *pSym++ = c; + else + pSrc--; + eState = ssStop; + } + } +Label_MaskStateMachine: + switch (eState) + { + case ssGetChar : + { + // Order is important! + if (eLastOp == ocTableRefOpen && c != '[' && c != '#' && c != ']') + { + *pSym++ = c; + eState = ssGetTableRefColumn; + } + else if( nMask & ScCharFlags::OdfLabelOp ) + { + // '!!' automatic intersection + if (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::OdfLabelOp) + { + /* TODO: For now the UI "space operator" is used, this + * could be enhanced using a specialized OpCode to get + * rid of the space ambiguity, which would need some + * places to be adapted though. And we would still need + * to support the ambiguous space operator for UI + * purposes anyway. However, we then could check for + * invalid usage of '!!', which currently isn't + * possible. */ + if (!bAutoIntersection) + { + ++pSrc; + // Add 2 because it must match the character count + // for bi18n. + addWhitespace( vSpaces, aSpace, 0x20, 2); + // Position of Whitespace where it will be added to + // vector. + nAutoIntersectionSpacesPos = vSpaces.size(); + bAutoIntersection = true; + } + else + { + pSrc--; + eState = ssStop; + } + } + else + { + nMask &= ~ScCharFlags::OdfLabelOp; + goto Label_MaskStateMachine; + } + } + else if( nMask & ScCharFlags::OdfNameMarker ) + { + // '$$' defined name marker + if (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::OdfNameMarker) + { + // both eaten, not added to pSym + ++pSrc; + } + else + { + nMask &= ~ScCharFlags::OdfNameMarker; + goto Label_MaskStateMachine; + } + } + else if( nMask & ScCharFlags::Char ) + { + // '[' is a special case in Excel syntax, it can start an + // external reference, ID in OOXML like [1]Sheet1!A1 or + // Excel_A1 [filename]Sheet!A1 or Excel_R1C1 + // [filename]Sheet!R1C1 that needs to be scanned + // entirely, or can be ocTableRefOpen, of which the first + // transforms an ocDBArea into an ocTableRef. + if (c == '[' && FormulaGrammar::isExcelSyntax( meGrammar) + && eLastOp != ocDBArea && maTableRefs.empty()) + { + // [0]!Global_Range_Name, is a special case in OOXML + // syntax, where the '0' is referencing to self and we + // do not need it, so we should skip it, in order to + // later it will be more recognisable for IsNamedRange. + if (FormulaGrammar::isRefConventionOOXML(meGrammar) && + pSrc[0] == '0' && pSrc[1] == ']' && pSrc[2] == '!') + { + pSrc += 3; + c = *pSrc; + continue; + } + + nMask &= ~ScCharFlags::Char; + goto Label_MaskStateMachine; + } + else + { + *pSym++ = c; + eState = ssStop; + } + } + else if( nMask & ScCharFlags::OdfLBracket ) + { + // eaten, not added to pSym + eState = ssGetReference; + mnPredetectedReference = 1; + } + else if( nMask & ScCharFlags::CharBool ) + { + *pSym++ = c; + eState = ssGetBool; + } + else if( nMask & ScCharFlags::CharValue ) + { + *pSym++ = c; + eState = ssGetValue; + } + else if( nMask & ScCharFlags::CharString ) + { + *pSym++ = c; + eState = ssGetString; + } + else if( nMask & ScCharFlags::CharErrConst ) + { + *pSym++ = c; + if (!maTableRefs.empty() && maTableRefs.back().mnLevel == 2) + eState = ssGetTableRefItem; + else + eState = ssGetErrorConstant; + } + else if( nMask & ScCharFlags::CharDontCare ) + { + addWhitespace( vSpaces, aSpace, c); + } + else if( nMask & ScCharFlags::CharIdent ) + { // try to get a simple ASCII identifier before calling + // i18n, to gain performance during import + *pSym++ = c; + eState = ssGetIdent; + } + else + { + bi18n = true; + eState = ssStop; + } + } + break; + case ssGetIdent: + { + if ( nMask & ScCharFlags::Ident ) + { // This catches also $Sheet1.A$1, for example. + if( pSym == &cSymbol[ MAXSTRLEN ] ) + { + SetError(FormulaError::StringOverflow); + eState = ssStop; + } + else + *pSym++ = c; + } + else if (c == '#' && lcl_isUnicodeIgnoreAscii( pSrc, "REF!", 4)) + { + // Completely ugly means to catch broken + // [$]#REF!.[$]#REF![$]#REF! (one or multiple parts) + // references that were written in ODF named ranges + // (without embracing [] hence no predetected reference) + // and to OOXML and handle them as one symbol. + // Also catches these in UI, so we can process them + // further. + int i = 0; + for ( ; i<5; ++i) + { + if( pSym == &cSymbol[ MAXSTRLEN ] ) + { + SetError(FormulaError::StringOverflow); + eState = ssStop; + break; // for + } + else + { + *pSym++ = c; + c = *pSrc++; + } + } + if (i == 5) + c = *((--pSrc)-1); // position last/next character correctly + } + else if (c == ':' && mnRangeOpPosInSymbol < 0) + { + // One range operator may form Sheet1.A:A, which we need to + // pass as one entity to IsReference(). + if( pSym == &cSymbol[ MAXSTRLEN ] ) + { + SetError(FormulaError::StringOverflow); + eState = ssStop; + } + else + { + mnRangeOpPosInSymbol = pSym - &cSymbol[0]; + *pSym++ = c; + } + } + else if ( 128 <= c || '\'' == c ) + { // High values need reparsing with i18n, + // single quoted $'sheet' names too (otherwise we'd had to + // implement everything twice). + bi18n = true; + eState = ssStop; + } + else + { + pSrc--; + eState = ssStop; + } + } + break; + case ssGetBool : + { + if( nMask & ScCharFlags::Bool ) + { + *pSym++ = c; + eState = ssStop; + } + else + { + pSrc--; + eState = ssStop; + } + } + break; + case ssGetValue : + { + if( pSym == &cSymbol[ MAXSTRLEN ] ) + { + SetError(FormulaError::StringOverflow); + eState = ssStop; + } + else if (c == cDecSep || (cDecSepAlt && c == cDecSepAlt)) + { + if (++nDecSeps > 1) + { + // reparse with i18n, may be numeric sheet name as well + bi18n = true; + eState = ssStop; + } + else + *pSym++ = c; + } + else if( nMask & ScCharFlags::Value ) + *pSym++ = c; + else if( nMask & ScCharFlags::ValueSep ) + { + pSrc--; + eState = ssStop; + } + else if (c == 'E' || c == 'e') + { + if (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::ValueExp) + *pSym++ = c; + else + { + // reparse with i18n + bi18n = true; + eState = ssStop; + } + } + else if( nMask & ScCharFlags::ValueSign ) + { + if (((cLast == 'E') || (cLast == 'e')) && + (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::ValueValue)) + { + *pSym++ = c; + } + else + { + pSrc--; + eState = ssStop; + } + } + else + { + // reparse with i18n + bi18n = true; + eState = ssStop; + } + } + break; + case ssGetString : + { + if( nMask & ScCharFlags::StringSep ) + { + if ( !bQuote ) + { + if ( *pSrc == '"' ) + bQuote = true; // "" => literal " + else + eState = ssStop; + } + else + bQuote = false; + } + if ( !bQuote ) + { + if( pSym == &cSymbol[ MAXSTRLEN ] ) + { + SetError(FormulaError::StringOverflow); + eState = ssSkipString; + } + else + *pSym++ = c; + } + } + break; + case ssSkipString: + if( nMask & ScCharFlags::StringSep ) + eState = ssStop; + break; + case ssGetErrorConstant: + { + // ODFF Error ::= '#' [A-Z0-9]+ ([!?] | ('/' ([A-Z] | ([0-9] [!?])))) + // BUT, in UI these may have been translated! So don't + // check for ASCII alnum. Note that this construct can't be + // parsed with i18n. + /* TODO: be strict when reading ODFF, check for ASCII alnum + * and proper continuation after '/'. However, even with + * the lax parsing only the error constants we have defined + * as opcode symbols will be recognized and others result + * in ocBad, so the result is actually conformant. */ + bool bAdd = true; + if ('?' == c) + eState = ssStop; + else if ('!' == c) + { + // Check if this is #REF! that starts an invalid reference. + // Note we have an implicit '!' here at the end. + if (pSym - &cSymbol[0] == 4 && lcl_isUnicodeIgnoreAscii( cSymbol, "#REF", 4) && + (GetCharTableFlags( *pSrc, c) & ScCharFlags::Ident)) + eState = ssGetIdent; + else + eState = ssStop; + } + else if ('/' == c) + { + if (!bErrorConstantHadSlash) + bErrorConstantHadSlash = true; + else + { + bAdd = false; + eState = ssStop; + } + } + else if ((nMask & ScCharFlags::WordSep) || + (c < 128 && !rtl::isAsciiAlphanumeric( c))) + { + bAdd = false; + eState = ssStop; + } + if (!bAdd) + --pSrc; + else + { + if (pSym == &cSymbol[ MAXSTRLEN ]) + { + SetError( FormulaError::StringOverflow); + eState = ssStop; + } + else + *pSym++ = c; + } + } + break; + case ssGetTableRefItem: + { + // Scan whatever up to the next ']' closer. + if (c != ']') + { + if( pSym == &cSymbol[ MAXSTRLEN ] ) + { + SetError( FormulaError::StringOverflow); + eState = ssStop; + } + else + *pSym++ = c; + } + else + { + --pSrc; + eState = ssStop; + } + } + break; + case ssGetTableRefColumn: + { + // Scan whatever up to the next unescaped ']' closer. + if (c != ']' || cLast == '\'') + { + if( pSym == &cSymbol[ MAXSTRLEN ] ) + { + SetError( FormulaError::StringOverflow); + eState = ssStop; + } + else + *pSym++ = c; + } + else + { + --pSrc; + eState = ssStop; + } + } + break; + case ssGetReference: + if( pSym == &cSymbol[ MAXSTRLEN ] ) + { + SetError( FormulaError::StringOverflow); + eState = ssSkipReference; + } + [[fallthrough]]; + case ssSkipReference: + // ODF reference: ['External'#$'Sheet'.A1:.B2] with dots being + // mandatory also if no sheet name. 'External'# is optional, + // sheet name is optional, quotes around sheet name are + // optional if no quote contained. [#REF!] is valid. + // 2nd usage: ['Sheet'.$$'DefinedName'] + // 3rd usage: ['External'#$$'DefinedName'] + // 4th usage: ['External'#$'Sheet'.$$'DefinedName'] + // Also for all these names quotes are optional if no quote + // contained. + { + + // nRefInName: 0 := not in sheet name yet. 'External' + // is parsed as if it was a sheet name and nRefInName + // is reset when # is encountered immediately after closing + // quote. Same with 'DefinedName', nRefInName is cleared + // when : is encountered. + + // Encountered leading $ before sheet name. + constexpr int kDollar = (1 << 1); + // Encountered ' opening quote, which may be after $ or + // not. + constexpr int kOpen = (1 << 2); + // Somewhere in name. + constexpr int kName = (1 << 3); + // Encountered ' in name, will be cleared if double or + // transformed to kClose if not, in which case kOpen is + // cleared. + constexpr int kQuote = (1 << 4); + // Past ' closing quote. + constexpr int kClose = (1 << 5); + // Encountered # file/sheet separator. + constexpr int kFileSep = (1 << 6); + // Past . sheet name separator. + constexpr int kPast = (1 << 7); + // Marked name $$ follows sheet name separator, detected + // while we're still on the separator. Will be cleared when + // entering the name. + constexpr int kMarkAhead = (1 << 8); + // In marked defined name. + constexpr int kDefName = (1 << 9); + // Encountered # of #REF! + constexpr int kRefErr = (1 << 10); + + bool bAddToSymbol = true; + if ((nMask & ScCharFlags::OdfRBracket) && !(nRefInName & kOpen)) + { + OSL_ENSURE( nRefInName & (kPast | kDefName | kRefErr), + "ScCompiler::NextSymbol: reference: " + "closing bracket ']' without prior sheet name separator '.' violates ODF spec"); + // eaten, not added to pSym + bAddToSymbol = false; + eState = ssStop; + } + else if (cSheetSep == c && nRefInName == 0) + { + // eat it, no sheet name [.A1] + bAddToSymbol = false; + nRefInName |= kPast; + if ('$' == pSrc[0] && '$' == pSrc[1]) + nRefInName |= kMarkAhead; + } + else if (!(nRefInName & kPast) || (nRefInName & (kMarkAhead | kDefName))) + { + // Not in col/row yet. + + if (SC_COMPILER_FILE_TAB_SEP == c && (nRefInName & kFileSep)) + nRefInName = 0; + else if ('$' == c && '$' == pSrc[0] && !(nRefInName & kOpen)) + { + nRefInName &= ~kMarkAhead; + if (!(nRefInName & kDefName)) + { + // eaten, not added to pSym (2 chars) + bAddToSymbol = false; + ++pSrc; + nRefInName &= kPast; + nRefInName |= kDefName; + } + else + { + // ScAddress::Parse() will recognize this as + // invalid later. + if (eState != ssSkipReference) + { + *pSym++ = c; + + if( pSym == &cSymbol[ MAXSTRLEN ] ) + { + SetError( FormulaError::StringOverflow); + eState = ssStop; + } + else + *pSym++ = *pSrc++; + } + bAddToSymbol = false; + } + } + else if (cSheetPrefix == c && nRefInName == 0) + nRefInName |= kDollar; + else if ('\'' == c) + { + // TODO: The conventions' parseExternalName() + // should handle quoted names, but as long as they + // don't remove non-embedded quotes here. + if (!(nRefInName & kName)) + { + nRefInName |= (kOpen | kName); + bAddToSymbol = !(nRefInName & kDefName); + } + else if (!(nRefInName & kOpen)) + { + OSL_FAIL("ScCompiler::NextSymbol: reference: " + "a ''' without the name being enclosed in '...' violates ODF spec"); + } + else if (nRefInName & kQuote) + { + // escaped embedded quote + nRefInName &= ~kQuote; + } + else + { + switch (pSrc[0]) + { + case '\'': + // escapes embedded quote + nRefInName |= kQuote; + break; + case SC_COMPILER_FILE_TAB_SEP: + // sheet name should follow + nRefInName |= kFileSep; + [[fallthrough]]; + default: + // quote not followed by quote => close + nRefInName |= kClose; + nRefInName &= ~kOpen; + } + bAddToSymbol = !(nRefInName & kDefName); + } + } + else if ('#' == c && nRefInName == 0) + nRefInName |= kRefErr; + else if (cSheetSep == c && !(nRefInName & kOpen)) + { + // unquoted sheet name separator + nRefInName |= kPast; + if ('$' == pSrc[0] && '$' == pSrc[1]) + nRefInName |= kMarkAhead; + } + else if (':' == c && !(nRefInName & kOpen)) + { + OSL_FAIL("ScCompiler::NextSymbol: reference: " + "range operator ':' without prior sheet name separator '.' violates ODF spec"); + nRefInName = 0; + ++mnPredetectedReference; + } + else if (!(nRefInName & kName)) + { + // start unquoted name + nRefInName |= kName; + } + } + else if (':' == c) + { + // range operator + nRefInName = 0; + ++mnPredetectedReference; + } + if (bAddToSymbol && eState != ssSkipReference) + *pSym++ = c; // everything is part of reference + } + break; + case ssStop: + ; // nothing, prevent warning + break; + } + cLast = c; + c = *pSrc; + } + + if (aSpace.nCount && aSpace.cChar) + vSpaces.emplace_back(aSpace); + + if ( bi18n ) + { + const sal_Int32 nOldSrcPos = nSrcPos; + for (const auto& r : vSpaces) + nSrcPos += r.nCount; + // If group separator is not a possible operator and not one of any + // separators then it may be parsed away in numbers. This is + // specifically the case with NO-BREAK SPACE, which actually triggers + // the bi18n case (which we don't want to include as yet another + // special case above as it is rare enough and doesn't generally occur + // in formulas). + const sal_Unicode cGroupSep = ScGlobal::getLocaleData().getNumThousandSep()[0]; + const bool bGroupSeparator = (128 <= cGroupSep && cGroupSep != cSep && + cGroupSep != cArrayColSep && cGroupSep != cArrayRowSep && + cGroupSep != cDecSep && cGroupSep != cDecSepAlt && + cGroupSep != cSheetPrefix && cGroupSep != cSheetSep); + // If a numeric context triggered bi18n then use the default locale's + // CharClass, this may accept group separator as well. + const CharClass* pMyCharClass = (ScGlobal::getCharClass().isDigit( OUString(pStart[nSrcPos]), 0) ? + &ScGlobal::getCharClass() : pCharClass); + OUStringBuffer aSymbol; + mnRangeOpPosInSymbol = -1; + FormulaError nErr = FormulaError::NONE; + do + { + bi18n = false; + // special case (e.g. $'sheetname' in OOO A1) + if ( pStart[nSrcPos] == cSheetPrefix && pStart[nSrcPos+1] == '\'' ) + aSymbol.append(pStart[nSrcPos++]); + + ParseResult aRes = pConv->parseAnyToken( aFormula, nSrcPos, pMyCharClass, bGroupSeparator); + + if ( !aRes.TokenType ) + { + nErr = FormulaError::IllegalChar; + SetError( nErr ); // parsed chars as string + } + if ( aRes.EndPos <= nSrcPos ) + { + // Could not parse anything meaningful. + assert(!aRes.TokenType); + nErr = FormulaError::IllegalChar; + SetError( nErr ); + // Caller has to act on an empty symbol for + // nSrcPos < aFormula.getLength() + nSrcPos = nOldSrcPos; + aSymbol.setLength(0); + } + else + { + // When having parsed a second reference part, ensure that the + // i18n parser did not mistakenly parse a number that included + // a separator which happened to be meant as a parameter + // separator instead. + if (mnRangeOpPosInSymbol >= 0 && (aRes.TokenType & KParseType::ASC_NUMBER)) + { + for (sal_Int32 i = nSrcPos; i < aRes.EndPos; ++i) + { + if (pStart[i] == cSep) + aRes.EndPos = i; // also ends for + } + } + aSymbol.append( pStart + nSrcPos, aRes.EndPos - nSrcPos); + nSrcPos = aRes.EndPos; + c = pStart[nSrcPos]; + if ( aRes.TokenType & KParseType::SINGLE_QUOTE_NAME ) + { // special cases (e.g. 'sheetname'. or 'filename'# in OOO A1) + bi18n = (c == cSheetSep || c == SC_COMPILER_FILE_TAB_SEP); + } + // One range operator restarts parsing for second reference. + if (c == ':' && mnRangeOpPosInSymbol < 0) + { + mnRangeOpPosInSymbol = aSymbol.getLength(); + bi18n = true; + } + if ( bi18n ) + aSymbol.append(pStart[nSrcPos++]); + } + } while ( bi18n && nErr == FormulaError::NONE ); + sal_Int32 nLen = aSymbol.getLength(); + if ( nLen > MAXSTRLEN ) + { + SetError( FormulaError::StringOverflow ); + nLen = MAXSTRLEN; + } + if (mnRangeOpPosInSymbol >= nLen) + mnRangeOpPosInSymbol = -1; + lcl_UnicodeStrNCpy( cSymbol, aSymbol.getStr(), nLen ); + pSym = &cSymbol[nLen]; + } + else + { + nSrcPos = pSrc - pStart; + *pSym = 0; + } + if (mnRangeOpPosInSymbol >= 0 && mnRangeOpPosInSymbol == (pSym-1) - &cSymbol[0]) + { + // This is a trailing range operator, which is nonsense. Will be caught + // in next round. + mnRangeOpPosInSymbol = -1; + *--pSym = 0; + --nSrcPos; + } + if ( bAutoCorrect ) + aCorrectedSymbol = OUString(cSymbol, pSym - cSymbol); + if (bAutoIntersection && vSpaces[nAutoIntersectionSpacesPos].nCount > 1) + --vSpaces[nAutoIntersectionSpacesPos].nCount; // replace '!!' with only one space + return vSpaces; +} + +// Convert symbol to token + +bool ScCompiler::ParseOpCode( const OUString& rName, bool bInArray ) +{ + OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName)); + bool bFound = (iLook != mxSymbols->getHashMap().end()); + if (bFound) + { + OpCode eOp = iLook->second; + if (bInArray) + { + if (rName == mxSymbols->getSymbol(ocArrayColSep)) + eOp = ocArrayColSep; + else if (rName == mxSymbols->getSymbol(ocArrayRowSep)) + eOp = ocArrayRowSep; + } + else if (eOp == ocArrayColSep || eOp == ocArrayRowSep) + { + if (rName == mxSymbols->getSymbol(ocSep)) + eOp = ocSep; + else if (rName == ";") + { + switch (FormulaGrammar::extractFormulaLanguage( meGrammar)) + { + // Only for languages/grammars that actually use ';' + // parameter separator. + case css::sheet::FormulaLanguage::NATIVE: + case css::sheet::FormulaLanguage::ENGLISH: + case css::sheet::FormulaLanguage::ODFF: + case css::sheet::FormulaLanguage::ODF_11: + eOp = ocSep; + } + } + } + else if (eOp == ocCeil && mxSymbols->isOOXML()) + { + // Ensure that _xlfn.CEILING.MATH maps to ocCeil_Math. ocCeil is + // unassigned for import. + eOp = ocCeil_Math; + } + else if (eOp == ocFloor && mxSymbols->isOOXML()) + { + // Ensure that _xlfn.FLOOR.MATH maps to ocFloor_Math. ocFloor is + // unassigned for import. + eOp = ocFloor_Math; + } + maRawToken.SetOpCode(eOp); + } + else if (mxSymbols->isODFF()) + { + // ODFF names that are not written in the current mapping but to be + // recognized. New names will be written in a future release, then + // exchange (!) with the names in + // formula/source/core/resource/core_resource.src to be able to still + // read the old names as well. + struct FunctionName + { + const char* pName; + OpCode eOp; + }; + static const FunctionName aOdffAliases[] = { + // Renamed old names, still accept them: + { "B", ocB }, // B -> BINOM.DIST.RANGE + { "TDIST", ocTDist }, // TDIST -> LEGACY.TDIST + { "EASTERSUNDAY", ocEasterSunday }, // EASTERSUNDAY -> ORG.OPENOFFICE.EASTERSUNDAY + { "ZGZ", ocRRI }, // ZGZ -> RRI + { "COLOR", ocColor }, // COLOR -> ORG.LIBREOFFICE.COLOR + { "GOALSEEK", ocBackSolver }, // GOALSEEK -> ORG.OPENOFFICE.GOALSEEK + { "COM.MICROSOFT.F.DIST", ocFDist_LT }, // fdo#40835, -> FDIST -> COM.MICROSOFT.F.DIST + { "COM.MICROSOFT.F.INV", ocFInv_LT } // tdf#94214, COM.MICROSOFT.F.INV -> FINV (ODF) + // Renamed new names, prepare to read future names: + //{ "ORG.OPENOFFICE.XXX", ocXXX } // XXX -> ORG.OPENOFFICE.XXX + }; + for (const FunctionName& rOdffAlias : aOdffAliases) + { + if (rName.equalsIgnoreAsciiCaseAscii( rOdffAlias.pName)) + { + maRawToken.SetOpCode( rOdffAlias.eOp); + bFound = true; + break; // for + } + } + } + else if (mxSymbols->isOOXML()) + { + // OOXML names that are not written in the current mapping but to be + // recognized as old versions wrote them. + struct FunctionName + { + const char* pName; + OpCode eOp; + }; + static const FunctionName aOoxmlAliases[] = { + { "EFFECTIVE", ocEffect }, // EFFECTIVE -> EFFECT + { "ERRORTYPE", ocErrorType }, // ERRORTYPE -> _xlfn.ORG.OPENOFFICE.ERRORTYPE + { "MULTIRANGE", ocMultiArea }, // MULTIRANGE -> _xlfn.ORG.OPENOFFICE.MULTIRANGE + { "GOALSEEK", ocBackSolver }, // GOALSEEK -> _xlfn.ORG.OPENOFFICE.GOALSEEK + { "EASTERSUNDAY", ocEasterSunday }, // EASTERSUNDAY -> _xlfn.ORG.OPENOFFICE.EASTERSUNDAY + { "CURRENT", ocCurrent }, // CURRENT -> _xlfn.ORG.OPENOFFICE.CURRENT + { "STYLE", ocStyle } // STYLE -> _xlfn.ORG.OPENOFFICE.STYLE + }; + for (const FunctionName& rOoxmlAlias : aOoxmlAliases) + { + if (rName.equalsIgnoreAsciiCaseAscii( rOoxmlAlias.pName)) + { + maRawToken.SetOpCode( rOoxmlAlias.eOp); + bFound = true; + break; // for + } + } + } + else if (mxSymbols->isPODF()) + { + // PODF names are ODF 1.0/1.1 and also used in API XFunctionAccess. + // We can't rename them in + // formula/source/core/resource/core_resource.src but can add + // additional names to be recognized here so they match the UI names if + // those are renamed. + struct FunctionName + { + const char* pName; + OpCode eOp; + }; + static const FunctionName aPodfAliases[] = { + { "EFFECT", ocEffect } // EFFECTIVE -> EFFECT + }; + for (const FunctionName& rPodfAlias : aPodfAliases) + { + if (rName.equalsIgnoreAsciiCaseAscii( rPodfAlias.pName)) + { + maRawToken.SetOpCode( rPodfAlias.eOp); + bFound = true; + break; // for + } + } + } + + if (!bFound) + { + OUString aIntName; + if (mxSymbols->hasExternals()) + { + // If symbols are set by filters get mapping to exact name. + ExternalHashMap::const_iterator iExt( + mxSymbols->getExternalHashMap().find( rName)); + if (iExt != mxSymbols->getExternalHashMap().end()) + { + if (ScGlobal::GetAddInCollection()->GetFuncData( (*iExt).second)) + aIntName = (*iExt).second; + } + } + else + { + // Old (deprecated) addins first for legacy. + if (ScGlobal::GetLegacyFuncCollection()->findByName(OUString(cSymbol))) + { + aIntName = cSymbol; + } + else + // bLocalFirst=false for (English) upper full original name + // (service.function) + aIntName = ScGlobal::GetAddInCollection()->FindFunction( + rName, !mxSymbols->isEnglish()); + } + if (!aIntName.isEmpty()) + { + maRawToken.SetExternal( aIntName ); // international name + bFound = true; + } + } + if (!bFound) + return false; + OpCode eOp = maRawToken.GetOpCode(); + if (eOp == ocSub || eOp == ocNegSub) + { + bool bShouldBeNegSub = + (eLastOp == ocOpen || eLastOp == ocSep || eLastOp == ocNegSub || + (SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_BIN_OP) || + eLastOp == ocArrayOpen || + eLastOp == ocArrayColSep || eLastOp == ocArrayRowSep); + if (bShouldBeNegSub && eOp == ocSub) + maRawToken.NewOpCode( ocNegSub ); + //TODO: if ocNegSub had ForceArray we'd have to set it here + else if (!bShouldBeNegSub && eOp == ocNegSub) + maRawToken.NewOpCode( ocSub ); + } + return bFound; +} + +bool ScCompiler::ParseOpCode2( std::u16string_view rName ) +{ + for (sal_uInt16 i = ocInternalBegin; i <= ocInternalEnd; i++) + { + if (o3tl::equalsAscii(rName, pInternal[i - ocInternalBegin])) + { + maRawToken.SetOpCode(static_cast<OpCode>(i)); + return true; + } + } + + return false; +} + +static bool lcl_ParenthesisFollows( const sal_Unicode* p ) +{ + while (*p == ' ') + p++; + return *p == '('; +} + +bool ScCompiler::ParseValue( const OUString& rSym ) +{ + const sal_Int32 nFormulaLanguage = FormulaGrammar::extractFormulaLanguage( GetGrammar()); + if (nFormulaLanguage == css::sheet::FormulaLanguage::ODFF || nFormulaLanguage == css::sheet::FormulaLanguage::OOXML) + { + // Speedup things for ODFF, only well-formed numbers, not locale + // dependent nor user input. + rtl_math_ConversionStatus eStatus; + sal_Int32 nParseEnd; + double fVal = rtl::math::stringToDouble( rSym, '.', 0, &eStatus, &nParseEnd); + if (nParseEnd != rSym.getLength()) + { + // Not (only) a number. + + if (nParseEnd > 0) + return false; // partially a number => no such thing + + if (lcl_ParenthesisFollows( aFormula.getStr() + nSrcPos)) + return false; // some function name, not a constant + + // Could be TRUE or FALSE constant. + OpCode eOpFunc = ocNone; + if (rSym.equalsIgnoreAsciiCase("TRUE")) + eOpFunc = ocTrue; + else if (rSym.equalsIgnoreAsciiCase("FALSE")) + eOpFunc = ocFalse; + if (eOpFunc != ocNone) + { + maRawToken.SetOpCode(eOpFunc); + // add missing trailing parentheses + maPendingOpCodes.push(ocOpen); + maPendingOpCodes.push(ocClose); + return true; + } + return false; + } + if (eStatus == rtl_math_ConversionStatus_OutOfRange) + { + // rtl::math::stringToDouble() recognizes XMLSchema-2 "INF" and + // "NaN" (case sensitive) that could be named expressions or DB + // areas as well. + // rSym is already upper so "NaN" is not possible here. + if (!std::isfinite(fVal) && rSym == "INF") + { + SCTAB nSheet = -1; + if (GetRangeData( nSheet, rSym)) + return false; + if (rDoc.GetDBCollection()->getNamedDBs().findByUpperName(rSym)) + return false; + } + /* TODO: is there a specific reason why we don't accept an infinity + * value that would raise an error in the interpreter, instead of + * setting the hard error at the token array already? */ + SetError( FormulaError::IllegalArgument ); + } + maRawToken.SetDouble( fVal ); + return true; + } + + double fVal; + sal_uInt32 nIndex = mxSymbols->isEnglishLocale() ? mpFormatter->GetStandardIndex(LANGUAGE_ENGLISH_US) : 0; + + if (!mpFormatter->IsNumberFormat(rSym, nIndex, fVal)) + return false; + + SvNumFormatType nType = mpFormatter->GetType(nIndex); + + // Don't accept 3:3 as time, it is a reference to entire row 3 instead. + // Dates should never be entered directly and automatically converted + // to serial, because the serial would be wrong if null-date changed. + // Usually it wouldn't be accepted anyway because the date separator + // clashed with other separators or operators. + if (nType & (SvNumFormatType::TIME | SvNumFormatType::DATE)) + return false; + + if (nType == SvNumFormatType::LOGICAL) + { + if (lcl_ParenthesisFollows( aFormula.getStr() + nSrcPos)) + return false; // Boolean function instead. + } + + if( nType == SvNumFormatType::TEXT ) + // HACK: number too big! + SetError( FormulaError::IllegalArgument ); + maRawToken.SetDouble( fVal ); + return true; +} + +bool ScCompiler::ParseString() +{ + if ( cSymbol[0] != '"' ) + return false; + const sal_Unicode* p = cSymbol+1; + while ( *p ) + p++; + sal_Int32 nLen = sal::static_int_cast<sal_Int32>( p - cSymbol - 1 ); + if (!nLen || cSymbol[nLen] != '"') + return false; + svl::SharedString aSS = rDoc.GetSharedStringPool().intern(OUString(cSymbol+1, nLen-1)); + maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase()); + return true; +} + +bool ScCompiler::ParsePredetectedErrRefReference( const OUString& rName, const OUString* pErrRef ) +{ + switch (mnPredetectedReference) + { + case 1: + return ParseSingleReference( rName, pErrRef); + case 2: + return ParseDoubleReference( rName, pErrRef); + default: + return false; + } +} + +bool ScCompiler::ParsePredetectedReference( const OUString& rName ) +{ + // Speedup documents with lots of broken references, e.g. sheet deleted. + // It could also be a broken invalidated reference that contains #REF! + // (but is not equal to), which we wrote prior to ODFF and also to ODFF + // between 2013 and 2016 until 5.1.4 + constexpr OUString aErrRef(u"#REF!"_ustr); // not localized in ODFF + sal_Int32 nPos = rName.indexOf( aErrRef); + if (nPos != -1) + { + /* TODO: this may be enhanced by reusing scan information from + * NextSymbol(), the positions of quotes and special characters found + * there for $'sheet'.A1:... could be stored in a vector. We don't + * fully rescan here whether found positions are within single quotes + * for performance reasons. This code does not check for possible + * occurrences of insane "valid" sheet names like + * 'haha.#REF!1fooledyou' and will generate an error on such. */ + if (nPos == 0) + { + // Per ODFF the correct string for a reference error is just #REF!, + // so pass it on. + if (rName.getLength() == 5) + return ParseErrorConstant( rName); + // #REF!.AB42 or #REF!42 or #REF!#REF! + return ParsePredetectedErrRefReference( rName, &aErrRef); + } + sal_Unicode c = rName[nPos-1]; // before #REF! + if ('$' == c) + { + if (nPos == 1) + { + // $#REF!.AB42 or $#REF!42 or $#REF!#REF! + return ParsePredetectedErrRefReference( rName, &aErrRef); + } + c = rName[nPos-2]; // before $#REF! + } + sal_Unicode c2 = nPos+5 < rName.getLength() ? rName[nPos+5] : 0; // after #REF! + switch (c) + { + case '.': + if ('$' == c2 || '#' == c2 || ('0' <= c2 && c2 <= '9')) + { + // sheet.#REF!42 or sheet.#REF!#REF! + return ParsePredetectedErrRefReference( rName, &aErrRef); + } + break; + case ':': + if (mnPredetectedReference > 1 && + ('.' == c2 || '$' == c2 || '#' == c2 || + ('0' <= c2 && c2 <= '9'))) + { + // :#REF!.AB42 or :#REF!42 or :#REF!#REF! + return ParsePredetectedErrRefReference( rName, &aErrRef); + } + break; + default: + if (rtl::isAsciiAlpha(c) && + ((mnPredetectedReference > 1 && ':' == c2) || 0 == c2)) + { + // AB#REF!: or AB#REF! + return ParsePredetectedErrRefReference( rName, &aErrRef); + } + } + } + switch (mnPredetectedReference) + { + case 1: + return ParseSingleReference( rName); + case 2: + return ParseDoubleReference( rName); + } + return false; +} + +bool ScCompiler::ParseDoubleReference( const OUString& rName, const OUString* pErrRef ) +{ + ScRange aRange( aPos, aPos ); + const ScAddress::Details aDetails( pConv->meConv, aPos ); + ScAddress::ExternalInfo aExtInfo; + ScRefFlags nFlags = aRange.Parse( rName, rDoc, aDetails, &aExtInfo, &maExternalLinks, pErrRef ); + if( nFlags & ScRefFlags::VALID ) + { + ScComplexRefData aRef; + aRef.InitRange( aRange ); + aRef.Ref1.SetColRel( (nFlags & ScRefFlags::COL_ABS) == ScRefFlags::ZERO ); + aRef.Ref1.SetRowRel( (nFlags & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO ); + aRef.Ref1.SetTabRel( (nFlags & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO ); + if ( !(nFlags & ScRefFlags::TAB_VALID) ) + aRef.Ref1.SetTabDeleted( true ); // #REF! + aRef.Ref1.SetFlag3D( ( nFlags & ScRefFlags::TAB_3D ) != ScRefFlags::ZERO ); + aRef.Ref2.SetColRel( (nFlags & ScRefFlags::COL2_ABS) == ScRefFlags::ZERO ); + aRef.Ref2.SetRowRel( (nFlags & ScRefFlags::ROW2_ABS) == ScRefFlags::ZERO ); + aRef.Ref2.SetTabRel( (nFlags & ScRefFlags::TAB2_ABS) == ScRefFlags::ZERO ); + if ( !(nFlags & ScRefFlags::TAB2_VALID) ) + aRef.Ref2.SetTabDeleted( true ); // #REF! + aRef.Ref2.SetFlag3D( ( nFlags & ScRefFlags::TAB2_3D ) != ScRefFlags::ZERO ); + aRef.SetRange(rDoc.GetSheetLimits(), aRange, aPos); + if (aExtInfo.mbExternal) + { + ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); + const OUString* pRealTab = pRefMgr->getRealTableName(aExtInfo.mnFileId, aExtInfo.maTabName); + maRawToken.SetExternalDoubleRef( + aExtInfo.mnFileId, pRealTab ? *pRealTab : aExtInfo.maTabName, aRef); + maExternalFiles.push_back(aExtInfo.mnFileId); + } + else + { + maRawToken.SetDoubleReference(aRef); + } + } + + return ( nFlags & ScRefFlags::VALID ) != ScRefFlags::ZERO; +} + +bool ScCompiler::ParseSingleReference( const OUString& rName, const OUString* pErrRef ) +{ + mnCurrentSheetEndPos = 0; + mnCurrentSheetTab = -1; + ScAddress aAddr( aPos ); + const ScAddress::Details aDetails( pConv->meConv, aPos ); + ScAddress::ExternalInfo aExtInfo; + ScRefFlags nFlags = aAddr.Parse( rName, rDoc, aDetails, + &aExtInfo, &maExternalLinks, &mnCurrentSheetEndPos, pErrRef); + // Something must be valid in order to recognize Sheet1.blah or blah.a1 + // as a (wrong) reference. + if( nFlags & ( ScRefFlags::COL_VALID|ScRefFlags::ROW_VALID|ScRefFlags::TAB_VALID ) ) + { + // Valid given tab and invalid col or row may indicate a sheet-local + // named expression, bail out early and don't create a reference token. + if (!(nFlags & ScRefFlags::VALID) && mnCurrentSheetEndPos > 0 && + (nFlags & ScRefFlags::TAB_VALID) && (nFlags & ScRefFlags::TAB_3D)) + { + if (aExtInfo.mbExternal) + { + // External names are handled separately. + mnCurrentSheetEndPos = 0; + mnCurrentSheetTab = -1; + } + else + { + mnCurrentSheetTab = aAddr.Tab(); + } + return false; + } + + if( HasPossibleNamedRangeConflict( aAddr.Tab())) + { + // A named range named e.g. 'num1' is valid with 1k columns, but would become a reference + // when the document is opened later with 16k columns. Resolve the conflict by not + // considering it a reference. + OUString aUpper( ScGlobal::getCharClass().uppercase( rName )); + mnCurrentSheetTab = aAddr.Tab(); // temporarily set for ParseNamedRange() + if(ParseNamedRange( aUpper, true )) // only check + return false; + mnCurrentSheetTab = -1; + } + + ScSingleRefData aRef; + aRef.InitAddress( aAddr ); + aRef.SetColRel( (nFlags & ScRefFlags::COL_ABS) == ScRefFlags::ZERO ); + aRef.SetRowRel( (nFlags & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO ); + aRef.SetTabRel( (nFlags & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO ); + aRef.SetFlag3D( ( nFlags & ScRefFlags::TAB_3D ) != ScRefFlags::ZERO ); + // the reference is really invalid + if( !( nFlags & ScRefFlags::VALID ) ) + { + if( !( nFlags & ScRefFlags::COL_VALID ) ) + aRef.SetColDeleted(true); + if( !( nFlags & ScRefFlags::ROW_VALID ) ) + aRef.SetRowDeleted(true); + if( !( nFlags & ScRefFlags::TAB_VALID ) ) + aRef.SetTabDeleted(true); + nFlags |= ScRefFlags::VALID; + } + aRef.SetAddress(rDoc.GetSheetLimits(), aAddr, aPos); + + if (aExtInfo.mbExternal) + { + ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); + const OUString* pRealTab = pRefMgr->getRealTableName(aExtInfo.mnFileId, aExtInfo.maTabName); + maRawToken.SetExternalSingleRef( + aExtInfo.mnFileId, pRealTab ? *pRealTab : aExtInfo.maTabName, aRef); + maExternalFiles.push_back(aExtInfo.mnFileId); + } + else + maRawToken.SetSingleReference(aRef); + } + + return ( nFlags & ScRefFlags::VALID ) != ScRefFlags::ZERO; +} + +bool ScCompiler::ParseReference( const OUString& rName, const OUString* pErrRef ) +{ + // Has to be called before ParseValue + + // A later ParseNamedRange() relies on these, being set in ParseSingleReference() + // if so, reset in all cases. + mnCurrentSheetEndPos = 0; + mnCurrentSheetTab = -1; + + sal_Unicode ch1 = rName[0]; + sal_Unicode cDecSep = ( mxSymbols->isEnglishLocale() ? '.' : ScGlobal::getLocaleData().getNumDecimalSep()[0] ); + if ( ch1 == cDecSep ) + return false; + // Code further down checks only if cDecSep=='.' so simply obtaining the + // alternative decimal separator if it's not is sufficient. + if (cDecSep != '.') + { + cDecSep = ScGlobal::getLocaleData().getNumDecimalSepAlt().toChar(); + if ( ch1 == cDecSep ) + return false; + } + // Who was that imbecile introducing '.' as the sheet name separator!?! + if ( rtl::isAsciiDigit( ch1 ) && pConv->getSpecialSymbol( Convention::SHEET_SEPARATOR) == '.' ) + { + // Numerical sheet name is valid. + // But English 1.E2 or 1.E+2 is value 100, 1.E-2 is 0.01 + // Don't create a #REF! of values. But also do not bail out on + // something like 3:3, meaning entire row 3. + do + { + const sal_Int32 nPos = ScGlobal::FindUnquoted( rName, '.'); + if ( nPos == -1 ) + { + if (ScGlobal::FindUnquoted( rName, ':') != -1) + break; // may be 3:3, continue as usual + return false; + } + sal_Unicode const * const pTabSep = rName.getStr() + nPos; + sal_Unicode ch2 = pTabSep[1]; // maybe a column identifier + if ( !(ch2 == '$' || rtl::isAsciiAlpha( ch2 )) ) + return false; + if ( cDecSep == '.' && (ch2 == 'E' || ch2 == 'e') // E + - digit + && (GetCharTableFlags( pTabSep[2], pTabSep[1] ) & ScCharFlags::ValueExp) ) + { + // If it is an 1.E2 expression check if "1" is an existent sheet + // name. If so, a desired value 1.E2 would have to be entered as + // 1E2 or 1.0E2 or 1.E+2, sorry. Another possibility would be to + // require numerical sheet names always being entered quoted, which + // is not desirable (too many 1999, 2000, 2001 sheets in use). + // Furthermore, XML files created with versions prior to SRC640e + // wouldn't contain the quotes added by MakeTabStr()/CheckTabQuotes() + // and would produce wrong formulas if the conditions here are met. + // If you can live with these restrictions you may remove the + // check and return an unconditional FALSE. + OUString aTabName( rName.copy( 0, nPos ) ); + SCTAB nTab; + if ( !rDoc.GetTable( aTabName, nTab ) ) + return false; + // If sheet "1" exists and the expression is 1.E+2 continue as + // usual, the ScRange/ScAddress parser will take care of it. + } + } while(false); + } + + if (ParseSingleReference( rName, pErrRef)) + return true; + + // Though the range operator is handled explicitly, when encountering + // something like Sheet1.A:A we will have to treat it as one entity if it + // doesn't pass as single cell reference. + if (mnRangeOpPosInSymbol > 0) // ":foo" would be nonsense + { + if (ParseDoubleReference( rName, pErrRef)) + return true; + // Now try with a symbol up to the range operator, rewind source + // position. + assert(mnRangeOpPosInSymbol < MAXSTRLEN); // We should have caught the maldoers. + if (mnRangeOpPosInSymbol >= MAXSTRLEN) // TODO: this check and return + return false; // can be removed when sure. + sal_Int32 nLen = mnRangeOpPosInSymbol; + while (cSymbol[++nLen]) + ; + cSymbol[mnRangeOpPosInSymbol] = 0; + nSrcPos -= (nLen - mnRangeOpPosInSymbol); + mnRangeOpPosInSymbol = -1; + mbRewind = true; + return true; // end all checks + } + else + { + switch (pConv->meConv) + { + case FormulaGrammar::CONV_XL_A1: + case FormulaGrammar::CONV_XL_OOX: + // Special treatment for the 'E:\[doc]Sheet1:Sheet3'!D5 Excel + // sickness, mnRangeOpPosInSymbol did not catch the range + // operator as it is within a quoted name. + if (rName[0] != '\'') + return false; // Document name has to be single quoted. + [[fallthrough]]; + case FormulaGrammar::CONV_XL_R1C1: + // C2 or C[1] are valid entire column references. + if (ParseDoubleReference( rName, pErrRef)) + return true; + break; + default: + ; // nothing + } + } + return false; +} + +bool ScCompiler::ParseMacro( const OUString& rName ) +{ +#if !HAVE_FEATURE_SCRIPTING + (void) rName; + + return false; +#else + + // Calling SfxObjectShell::GetBasic() may result in all sort of things + // including obtaining the model and deep down in + // SfxBaseModel::getDocumentStorage() acquiring the SolarMutex, which when + // formulas are compiled from a threaded import may result in a deadlock. + // Check first if we actually could acquire it and if not bail out. + /* FIXME: yes, but how ... */ + vcl::SolarMutexTryAndBuyGuard g; + if (!g.isAcquired()) + { + SAL_WARN( "sc.core", "ScCompiler::ParseMacro - SolarMutex would deadlock, not obtaining Basic"); + return false; // bad luck + } + + OUString aName( rName); + StarBASIC* pObj = nullptr; + ScDocShell* pDocSh = rDoc.GetDocumentShell(); + + try + { + if( pDocSh )//XXX + pObj = pDocSh->GetBasic(); + else + pObj = SfxApplication::GetBasic(); + } + catch (...) + { + return false; + } + + if (!pObj) + return false; + + // ODFF recommends to store user-defined functions prefixed with "USER.", + // use only unprefixed name if encountered. BASIC doesn't allow '.' in a + // function name so a function "USER.FOO" could not exist, and macro check + // is assigned the lowest priority in function name check. + if (FormulaGrammar::isODFF( GetGrammar()) && aName.startsWithIgnoreAsciiCase("USER.")) + aName = aName.copy(5); + + SbxMethod* pMeth = static_cast<SbxMethod*>(pObj->Find( aName, SbxClassType::Method )); + if( !pMeth ) + { + return false; + } + // It really should be a BASIC function! + if( pMeth->GetType() == SbxVOID + || ( pMeth->IsFixed() && pMeth->GetType() == SbxEMPTY ) + || dynamic_cast<const SbMethod*>( pMeth) == nullptr ) + { + return false; + } + maRawToken.SetExternal( aName ); + maRawToken.eOp = ocMacro; + return true; +#endif +} + +const ScRangeData* ScCompiler::GetRangeData( SCTAB& rSheet, const OUString& rUpperName ) const +{ + // try local names first + rSheet = aPos.Tab(); + const ScRangeName* pRangeName = rDoc.GetRangeName(rSheet); + const ScRangeData* pData = nullptr; + if (pRangeName) + pData = pRangeName->findByUpperName(rUpperName); + if (!pData) + { + pRangeName = rDoc.GetRangeName(); + if (pRangeName) + pData = pRangeName->findByUpperName(rUpperName); + if (pData) + rSheet = -1; + } + return pData; +} + +bool ScCompiler::HasPossibleNamedRangeConflict( SCTAB nTab ) const +{ + const ScRangeName* pRangeName = rDoc.GetRangeName(); + if (pRangeName && pRangeName->hasPossibleAddressConflict()) + return true; + pRangeName = rDoc.GetRangeName(nTab); + if (pRangeName && pRangeName->hasPossibleAddressConflict()) + return true; + return false; +} + +bool ScCompiler::ParseNamedRange( const OUString& rUpperName, bool onlyCheck ) +{ + // ParseNamedRange is called only from NextNewToken, with an upper-case string + + SCTAB nSheet = -1; + const ScRangeData* pData = GetRangeData( nSheet, rUpperName); + if (pData) + { + if (!onlyCheck) + maRawToken.SetName( nSheet, pData->GetIndex()); + return true; + } + + // Sheet-local name with sheet specified. + if (mnCurrentSheetEndPos > 0 && mnCurrentSheetTab >= 0) + { + OUString aName( rUpperName.copy( mnCurrentSheetEndPos)); + const ScRangeName* pRangeName = rDoc.GetRangeName( mnCurrentSheetTab); + if (pRangeName) + { + pData = pRangeName->findByUpperName(aName); + if (pData) + { + if (!onlyCheck) + maRawToken.SetName( mnCurrentSheetTab, pData->GetIndex()); + return true; + } + } + } + + return false; +} + +bool ScCompiler::ParseExternalNamedRange( const OUString& rSymbol, bool& rbInvalidExternalNameRange ) +{ + /* FIXME: This code currently (2008-12-02T15:41+0100 in CWS mooxlsc) + * correctly parses external named references in OOo, as required per RFE + * #i3740#, just that we can't store them in ODF yet. We will need an OASIS + * spec first. Until then don't pretend to support external names that + * wouldn't survive a save and reload cycle, return false instead. */ + + rbInvalidExternalNameRange = false; + + if (!pConv) + return false; + + OUString aFile, aName; + if (!pConv->parseExternalName( rSymbol, aFile, aName, rDoc, &maExternalLinks)) + return false; + + if (aFile.getLength() > MAXSTRLEN || aName.getLength() > MAXSTRLEN) + return false; + + ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); + OUString aTmp = aFile; + pRefMgr->convertToAbsName(aTmp); + aFile = aTmp; + sal_uInt16 nFileId = pRefMgr->getExternalFileId(aFile); + if (!pRefMgr->isValidRangeName(nFileId, aName)) + { + rbInvalidExternalNameRange = true; + // range name doesn't exist in the source document. + return false; + } + + const OUString* pRealName = pRefMgr->getRealRangeName(nFileId, aName); + maRawToken.SetExternalName(nFileId, pRealName ? *pRealName : aTmp); + maExternalFiles.push_back(nFileId); + return true; +} + +bool ScCompiler::ParseDBRange( const OUString& rName ) +{ + ScDBCollection::NamedDBs& rDBs = rDoc.GetDBCollection()->getNamedDBs(); + const ScDBData* p = rDBs.findByUpperName(rName); + if (!p) + return false; + + maRawToken.SetName( -1, p->GetIndex()); // DB range is always global. + maRawToken.eOp = ocDBArea; + return true; +} + +bool ScCompiler::ParseColRowName( const OUString& rName ) +{ + bool bInList = false; + bool bFound = false; + ScSingleRefData aRef; + OUString aName( rName ); + DeQuote( aName ); + SCTAB nThisTab = aPos.Tab(); + for ( short jThisTab = 1; jThisTab >= 0 && !bInList; jThisTab-- ) + { // first check ranges on this sheet, in case of duplicated names + for ( short jRow=0; jRow<2 && !bInList; jRow++ ) + { + ScRangePairList* pRL; + if ( !jRow ) + pRL = rDoc.GetColNameRanges(); + else + pRL = rDoc.GetRowNameRanges(); + for ( size_t iPair = 0, nPairs = pRL->size(); iPair < nPairs && !bInList; ++iPair ) + { + const ScRangePair & rR = (*pRL)[iPair]; + const ScRange& rNameRange = rR.GetRange(0); + if ( jThisTab && (rNameRange.aStart.Tab() > nThisTab || + nThisTab > rNameRange.aEnd.Tab()) ) + continue; // for + ScCellIterator aIter( rDoc, rNameRange ); + for (bool bHas = aIter.first(); bHas && !bInList; bHas = aIter.next()) + { + // Don't crash if cell (via CompileNameFormula) encounters + // a formula cell without code and + // HasStringData/Interpret/Compile is executed and all that + // recursively... + // Furthermore, *this* cell won't be touched, since no RPN exists yet. + CellType eType = aIter.getType(); + bool bOk = false; + if (eType == CELLTYPE_FORMULA) + { + ScFormulaCell* pFC = aIter.getFormulaCell(); + bOk = (pFC->GetCode()->GetCodeLen() > 0) && (pFC->aPos != aPos); + } + else + bOk = true; + + if (bOk && aIter.hasString()) + { + OUString aStr = aIter.getString(); + if ( ScGlobal::GetTransliteration().isEqual( aStr, aName ) ) + { + aRef.InitFlags(); + if ( !jRow ) + aRef.SetColRel( true ); // ColName + else + aRef.SetRowRel( true ); // RowName + aRef.SetAddress(rDoc.GetSheetLimits(), aIter.GetPos(), aPos); + bInList = bFound = true; + } + } + } + } + } + } + if ( !bInList && rDoc.GetDocOptions().IsLookUpColRowNames() ) + { // search in current sheet + tools::Long nDistance = 0, nMax = 0; + tools::Long nMyCol = static_cast<tools::Long>(aPos.Col()); + tools::Long nMyRow = static_cast<tools::Long>(aPos.Row()); + bool bTwo = false; + ScAddress aOne( 0, 0, aPos.Tab() ); + ScAddress aTwo( rDoc.MaxCol(), rDoc.MaxRow(), aPos.Tab() ); + + ScAutoNameCache* pNameCache = rDoc.GetAutoNameCache(); + if ( pNameCache ) + { + // use GetNameOccurrences to collect all positions of aName on the sheet + // (only once), similar to the outer part of the loop in the "else" branch. + + const ScAutoNameAddresses& rAddresses = pNameCache->GetNameOccurrences( aName, aPos.Tab() ); + + // Loop through the found positions, similar to the inner part of the loop in the "else" branch. + // The order of addresses in the vector is the same as from ScCellIterator. + + for ( const ScAddress& aAddress : rAddresses ) + { + if ( bFound ) + { // stop if everything else is further away + if ( nMax < static_cast<tools::Long>(aAddress.Col()) ) + break; // aIter + } + if ( aAddress != aPos ) + { + // same treatment as in isEqual case below + + SCCOL nCol = aAddress.Col(); + SCROW nRow = aAddress.Row(); + tools::Long nC = nMyCol - nCol; + tools::Long nR = nMyRow - nRow; + if ( bFound ) + { + tools::Long nD = nC * nC + nR * nR; + if ( nD < nDistance ) + { + if ( nC < 0 || nR < 0 ) + { // right or below + bTwo = true; + aTwo.Set( nCol, nRow, aAddress.Tab() ); + nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) ); + nDistance = nD; + } + else if ( nRow >= aOne.Row() || nMyRow < static_cast<tools::Long>(aOne.Row()) ) + { + // upper left, only if not further up than the + // current entry and nMyRow is below (CellIter + // runs column-wise) + bTwo = false; + aOne.Set( nCol, nRow, aAddress.Tab() ); + nMax = std::max( nMyCol + nC, nMyRow + nR ); + nDistance = nD; + } + } + } + else + { + aOne.Set( nCol, nRow, aAddress.Tab() ); + nDistance = nC * nC + nR * nR; + nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) ); + + } + bFound = true; + } + } + } + else + { + ScCellIterator aIter( rDoc, ScRange( aOne, aTwo ) ); + for (bool bHas = aIter.first(); bHas; bHas = aIter.next()) + { + if ( bFound ) + { // stop if everything else is further away + if ( nMax < static_cast<tools::Long>(aIter.GetPos().Col()) ) + break; // aIter + } + CellType eType = aIter.getType(); + bool bOk = false; + if (eType == CELLTYPE_FORMULA) + { + ScFormulaCell* pFC = aIter.getFormulaCell(); + bOk = (pFC->GetCode()->GetCodeLen() > 0) && (pFC->aPos != aPos); + } + else + bOk = true; + + if (bOk && aIter.hasString()) + { + OUString aStr = aIter.getString(); + if ( ScGlobal::GetTransliteration().isEqual( aStr, aName ) ) + { + SCCOL nCol = aIter.GetPos().Col(); + SCROW nRow = aIter.GetPos().Row(); + tools::Long nC = nMyCol - nCol; + tools::Long nR = nMyRow - nRow; + if ( bFound ) + { + tools::Long nD = nC * nC + nR * nR; + if ( nD < nDistance ) + { + if ( nC < 0 || nR < 0 ) + { // right or below + bTwo = true; + aTwo.Set( nCol, nRow, aIter.GetPos().Tab() ); + nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) ); + nDistance = nD; + } + else if ( nRow >= aOne.Row() || nMyRow < static_cast<tools::Long>(aOne.Row()) ) + { + // upper left, only if not further up than the + // current entry and nMyRow is below (CellIter + // runs column-wise) + bTwo = false; + aOne.Set( nCol, nRow, aIter.GetPos().Tab() ); + nMax = std::max( nMyCol + nC, nMyRow + nR ); + nDistance = nD; + } + } + } + else + { + aOne.Set( nCol, nRow, aIter.GetPos().Tab() ); + nDistance = nC * nC + nR * nR; + nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) ); + } + bFound = true; + } + } + } + } + + if ( bFound ) + { + ScAddress aAdr; + if ( bTwo ) + { + if ( nMyCol >= static_cast<tools::Long>(aOne.Col()) && nMyRow >= static_cast<tools::Long>(aOne.Row()) ) + aAdr = aOne; // upper left takes precedence + else + { + if ( nMyCol < static_cast<tools::Long>(aOne.Col()) ) + { // two to the right + if ( nMyRow >= static_cast<tools::Long>(aTwo.Row()) ) + aAdr = aTwo; // directly right + else + aAdr = aOne; + } + else + { // two below or below and right, take the nearest + tools::Long nC1 = nMyCol - aOne.Col(); + tools::Long nR1 = nMyRow - aOne.Row(); + tools::Long nC2 = nMyCol - aTwo.Col(); + tools::Long nR2 = nMyRow - aTwo.Row(); + if ( nC1 * nC1 + nR1 * nR1 <= nC2 * nC2 + nR2 * nR2 ) + aAdr = aOne; + else + aAdr = aTwo; + } + } + } + else + aAdr = aOne; + aRef.InitAddress( aAdr ); + // Prioritize on column label; row label only if the next cell + // above/below the found label cell is text, or if both are not and + // the cell below is empty and the next cell to the right is + // numeric. + if ((aAdr.Row() < rDoc.MaxRow() && rDoc.HasStringData( + aAdr.Col(), aAdr.Row() + 1, aAdr.Tab())) + || (aAdr.Row() > 0 && rDoc.HasStringData( + aAdr.Col(), aAdr.Row() - 1, aAdr.Tab())) + || (aAdr.Row() < rDoc.MaxRow() && rDoc.GetRefCellValue( + ScAddress( aAdr.Col(), aAdr.Row() + 1, aAdr.Tab())).isEmpty() + && aAdr.Col() < rDoc.MaxCol() && rDoc.GetRefCellValue( + ScAddress( aAdr.Col() + 1, aAdr.Row(), aAdr.Tab())).hasNumeric())) + aRef.SetRowRel( true ); // RowName + else + aRef.SetColRel( true ); // ColName + aRef.SetAddress(rDoc.GetSheetLimits(), aAdr, aPos); + } + } + if ( bFound ) + { + maRawToken.SetSingleReference( aRef ); + maRawToken.eOp = ocColRowName; + return true; + } + else + return false; +} + +bool ScCompiler::ParseBoolean( const OUString& rName ) +{ + OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName ) ); + if( iLook != mxSymbols->getHashMap().end() && + ((*iLook).second == ocTrue || + (*iLook).second == ocFalse) ) + { + maRawToken.SetOpCode( (*iLook).second ); + return true; + } + else + return false; +} + +bool ScCompiler::ParseErrorConstant( const OUString& rName ) +{ + FormulaError nError = GetErrorConstant( rName); + if (nError != FormulaError::NONE) + { + maRawToken.SetErrorConstant( nError); + return true; + } + else + return false; +} + +bool ScCompiler::ParseTableRefItem( const OUString& rName ) +{ + bool bItem = false; + OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName)); + if (iLook != mxSymbols->getHashMap().end()) + { + // Only called when there actually is a current TableRef, hence + // accessing maTableRefs.back() is safe. + ScTableRefToken* p = maTableRefs.back().mxToken.get(); + assert(p); // not a ScTableRefToken can't be + + switch ((*iLook).second) + { + case ocTableRefItemAll: + bItem = true; + p->AddItem( ScTableRefToken::ALL); + break; + case ocTableRefItemHeaders: + bItem = true; + p->AddItem( ScTableRefToken::HEADERS); + break; + case ocTableRefItemData: + bItem = true; + p->AddItem( ScTableRefToken::DATA); + break; + case ocTableRefItemTotals: + bItem = true; + p->AddItem( ScTableRefToken::TOTALS); + break; + case ocTableRefItemThisRow: + bItem = true; + p->AddItem( ScTableRefToken::THIS_ROW); + break; + default: + ; + } + if (bItem) + maRawToken.SetOpCode( (*iLook).second ); + } + return bItem; +} + +namespace { +OUString unescapeTableRefColumnSpecifier( const OUString& rStr ) +{ + // '#', '[', ']' and '\'' are escaped with '\'' + + if (rStr.indexOf( '\'' ) < 0) + return rStr; + + const sal_Int32 n = rStr.getLength(); + OUStringBuffer aBuf( n ); + const sal_Unicode* p = rStr.getStr(); + const sal_Unicode* const pStop = p + n; + bool bEscaped = false; + for ( ; p < pStop; ++p) + { + const sal_Unicode c = *p; + if (bEscaped) + { + aBuf.append( c ); + bEscaped = false; + } + else if (c == '\'') + bEscaped = true; // unescaped escaping '\'' + else + aBuf.append( c ); + } + return aBuf.makeStringAndClear(); +} +} + +bool ScCompiler::ParseTableRefColumn( const OUString& rName ) +{ + // Only called when there actually is a current TableRef, hence + // accessing maTableRefs.back() is safe. + ScTableRefToken* p = maTableRefs.back().mxToken.get(); + assert(p); // not a ScTableRefToken can't be + + ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex( p->GetIndex()); + if (!pDBData) + return false; + + OUString aName( unescapeTableRefColumnSpecifier( rName)); + + ScRange aRange; + pDBData->GetArea( aRange); + aRange.aEnd.SetTab( aRange.aStart.Tab()); + aRange.aEnd.SetRow( aRange.aStart.Row()); + + // Prefer the stored internal table column name, which is also needed for + // named expressions during document load time when cell content isn't + // available yet. Also, avoiding a possible calculation step in case the + // header cell is a formula cell is "a good thing". + sal_Int32 nOffset = pDBData->GetColumnNameOffset( aName); + if (nOffset >= 0) + { + // This is sneaky... we always use the top row of the database range, + // regardless of whether it is a header row or not. Code evaluating + // this reference must take that into account and may have to act + // differently if it is a header-less table. Which are two places, + // HandleTableRef() (no change necessary there) and + // CreateStringFromSingleRef() (must not fallback to cell lookup). + ScSingleRefData aRef; + ScAddress aAdr( aRange.aStart); + aAdr.IncCol( nOffset); + aRef.InitAddress( aAdr); + maRawToken.SetSingleReference( aRef ); + return true; + } + + if (pDBData->HasHeader()) + { + // Quite similar to IsColRowName() but limited to one row of headers. + ScCellIterator aIter( rDoc, aRange); + for (bool bHas = aIter.first(); bHas; bHas = aIter.next()) + { + CellType eType = aIter.getType(); + bool bOk = false; + if (eType == CELLTYPE_FORMULA) + { + ScFormulaCell* pFC = aIter.getFormulaCell(); + bOk = (pFC->GetCode()->GetCodeLen() > 0) && (pFC->aPos != aPos); + } + else + bOk = true; + + if (bOk && aIter.hasString()) + { + OUString aStr = aIter.getString(); + if (ScGlobal::GetTransliteration().isEqual( aStr, aName)) + { + // If this is successful and the internal column name + // lookup was not, it may be worth a warning. + SAL_WARN("sc.core", "ScCompiler::IsTableRefColumn - falling back to cell lookup"); + + /* XXX NOTE: we could init the column as relative so copying a + * formula across columns would point to the relative column, + * but do it absolute because: + * a) it makes the reference work in named expressions without + * having to distinguish + * b) Excel does it the same. */ + ScSingleRefData aRef; + aRef.InitAddress( aIter.GetPos()); + maRawToken.SetSingleReference( aRef ); + return true; + } + } + } + } + + return false; +} + +void ScCompiler::SetAutoCorrection( bool bVal ) +{ + assert(mbJumpCommandReorder); + bAutoCorrect = bVal; + mbStopOnError = !bVal; +} + +void ScCompiler::AutoCorrectParsedSymbol() +{ + sal_Int32 nPos = aCorrectedSymbol.getLength(); + if ( !nPos ) + return; + + nPos--; + const sal_Unicode cQuote = '\"'; + const sal_Unicode cx = 'x'; + const sal_Unicode cX = 'X'; + sal_Unicode c1 = aCorrectedSymbol[0]; + sal_Unicode c2 = aCorrectedSymbol[nPos]; + sal_Unicode c2p = nPos > 0 ? aCorrectedSymbol[nPos-1] : 0; + if ( c1 == cQuote && c2 != cQuote ) + { // "... + // What's not a word doesn't belong to it. + // Don't be pedantic: c < 128 should be sufficient here. + while ( nPos && ((aCorrectedSymbol[nPos] < 128) && + ((GetCharTableFlags(aCorrectedSymbol[nPos], aCorrectedSymbol[nPos-1]) & + (ScCharFlags::Word | ScCharFlags::CharDontCare)) == ScCharFlags::NONE)) ) + nPos--; + if ( nPos == MAXSTRLEN - 1 ) + aCorrectedSymbol = aCorrectedSymbol.replaceAt( nPos, 1, rtl::OUStringChar(cQuote) ); // '"' the MAXSTRLENth character + else + aCorrectedSymbol = aCorrectedSymbol.replaceAt( nPos + 1, 0, rtl::OUStringChar(cQuote) ); + bCorrected = true; + } + else if ( c1 != cQuote && c2 == cQuote ) + { // ..." + aCorrectedSymbol = OUStringChar(cQuote) + aCorrectedSymbol; + bCorrected = true; + } + else if ( nPos == 0 && (c1 == cx || c1 == cX) ) + { // x => * + aCorrectedSymbol = mxSymbols->getSymbol(ocMul); + bCorrected = true; + } + else if ( (GetCharTableFlags( c1, 0 ) & ScCharFlags::CharValue) + && (GetCharTableFlags( c2, c2p ) & ScCharFlags::CharValue) ) + { + if ( aCorrectedSymbol.indexOf(cx) >= 0 ) // At least two tokens separated by cx + { // x => * + sal_Unicode c = mxSymbols->getSymbolChar(ocMul); + aCorrectedSymbol = aCorrectedSymbol.replaceAll(OUStringChar(cx), OUStringChar(c)); + bCorrected = true; + } + if ( aCorrectedSymbol.indexOf(cX) >= 0 ) // At least two tokens separated by cX + { // X => * + sal_Unicode c = mxSymbols->getSymbolChar(ocMul); + aCorrectedSymbol = aCorrectedSymbol.replaceAll(OUStringChar(cX), OUStringChar(c)); + bCorrected = true; + } + } + else + { + OUString aSymbol( aCorrectedSymbol ); + OUString aDoc; + if ( aSymbol[0] == '\'' ) + { + sal_Int32 nPosition = aSymbol.indexOf( "'#" ); + if (nPosition != -1) + { // Split off 'Doc'#, may be d:\... or whatever + aDoc = aSymbol.copy(0, nPosition + 2); + aSymbol = aSymbol.copy(nPosition + 2); + } + } + sal_Int32 nRefs = comphelper::string::getTokenCount(aSymbol, ':'); + bool bColons; + if ( nRefs > 2 ) + { // duplicated or too many ':'? B:2::C10 => B2:C10 + bColons = true; + sal_Int32 nIndex = 0; + OUString aTmp1( aSymbol.getToken( 0, ':', nIndex ) ); + sal_Int32 nLen1 = aTmp1.getLength(); + OUStringBuffer aSym; + OUString aTmp2; + bool bLastAlp = true; + sal_Int32 nStrip = 0; + sal_Int32 nCount = nRefs; + for ( sal_Int32 j=1; j<nCount; j++ ) + { + aTmp2 = aSymbol.getToken( 0, ':', nIndex ); + sal_Int32 nLen2 = aTmp2.getLength(); + if ( nLen1 || nLen2 ) + { + if ( nLen1 ) + { + aSym.append(aTmp1); + bLastAlp = CharClass::isAsciiAlpha( aTmp1 ); + } + if ( nLen2 ) + { + bool bNextNum = CharClass::isAsciiNumeric( aTmp2 ); + if ( bLastAlp == bNextNum && nStrip < 1 ) + { + // Must be alternating number/string, only + // strip within a reference. + nRefs--; + nStrip++; + } + else + { + if ( !aSym.isEmpty() && aSym[aSym.getLength()-1] != ':') + aSym.append(":"); + nStrip = 0; + } + bLastAlp = !bNextNum; + } + else + { // :: + nRefs--; + if ( nLen1 ) + { // B10::C10 ? append ':' on next round + if ( !bLastAlp && !CharClass::isAsciiNumeric( aTmp1 ) ) + nStrip++; + } + } + aTmp1 = aTmp2; + nLen1 = nLen2; + } + else + nRefs--; + } + aSymbol = aSym + aTmp1; + aSym.setLength(0); + } + else + bColons = false; + if ( nRefs && nRefs <= 2 ) + { // reference twisted? 4A => A4 etc. + OUString aTab[2], aRef[2]; + const ScAddress::Details aDetails( pConv->meConv, aPos ); + if ( nRefs == 2 ) + { + sal_Int32 nIdx{ 0 }; + aRef[0] = aSymbol.getToken( 0, ':', nIdx ); + aRef[1] = aSymbol.getToken( 0, ':', nIdx ); + } + else + aRef[0] = aSymbol; + + bool bChanged = false; + bool bOk = true; + ScRefFlags nMask = ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID; + for ( int j=0; j<nRefs; j++ ) + { + sal_Int32 nTmp = 0; + sal_Int32 nDotPos = -1; + while ( (nTmp = aRef[j].indexOf( '.', nTmp )) != -1 ) + nDotPos = nTmp++; // the last one counts + if ( nDotPos != -1 ) + { + aTab[j] = aRef[j].copy( 0, nDotPos + 1 ); // with '.' + aRef[j] = aRef[j].copy( nDotPos + 1 ); + } + OUString aOld( aRef[j] ); + OUStringBuffer aStr2; + const sal_Unicode* p = aRef[j].getStr(); + while ( *p && rtl::isAsciiDigit( *p ) ) + aStr2.append(*p++); + aRef[j] = OUString( p ); + aRef[j] += aStr2; + if ( bColons || aRef[j] != aOld ) + { + bChanged = true; + ScAddress aAdr; + bOk &= ((aAdr.Parse( aRef[j], rDoc, aDetails ) & nMask) == nMask); + } + } + if ( bChanged && bOk ) + { + aCorrectedSymbol = aDoc; + aCorrectedSymbol += aTab[0]; + aCorrectedSymbol += aRef[0]; + if ( nRefs == 2 ) + { + aCorrectedSymbol += ":"; + aCorrectedSymbol += aTab[1]; + aCorrectedSymbol += aRef[1]; + } + bCorrected = true; + } + } + } +} + +bool ScCompiler::ToUpperAsciiOrI18nIsAscii( OUString& rUpper, const OUString& rOrg ) const +{ + if (FormulaGrammar::isODFF( meGrammar) || FormulaGrammar::isOOXML( meGrammar)) + { + // ODFF and OOXML have defined sets of English function names, avoid + // i18n overhead. + rUpper = rOrg.toAsciiUpperCase(); + return true; + } + else + { + // One of localized or English. + rUpper = pCharClass->uppercase(rOrg); + return false; + } +} + +bool ScCompiler::NextNewToken( bool bInArray ) +{ + if (!maPendingOpCodes.empty()) + { + maRawToken.SetOpCode(maPendingOpCodes.front()); + maPendingOpCodes.pop(); + return true; + } + + bool bAllowBooleans = bInArray; + const std::vector<Whitespace> & vSpaces = NextSymbol(bInArray); + + if (!cSymbol[0]) + { + if (nSrcPos < aFormula.getLength()) + { + // Nothing could be parsed, remainder as bad string. + // NextSymbol() must had set an error for this. + assert( pArr->GetCodeError() != FormulaError::NONE); + const OUString aBad( aFormula.copy( nSrcPos)); + svl::SharedString aSS = rDoc.GetSharedStringPool().intern( aBad); + maRawToken.SetString( aSS.getData(), aSS.getDataIgnoreCase()); + maRawToken.NewOpCode( ocBad); + nSrcPos = aFormula.getLength(); + // Add bad string as last token. + return true; + } + return false; + } + + if (!vSpaces.empty()) + { + ScRawToken aToken; + for (const auto& rSpace : vSpaces) + { + if (rSpace.cChar == 0x20) + { + // For now keep this a FormulaByteToken for the nasty + // significant whitespace intersection. This probably can be + // changed to a FormulaSpaceToken but then other places may + // need to be adapted. + aToken.SetOpCode( ocSpaces ); + aToken.sbyte.cByte = static_cast<sal_uInt8>( std::min<sal_Int32>(rSpace.nCount, 255) ); + } + else + { + aToken.SetOpCode( ocWhitespace ); + aToken.whitespace.nCount = static_cast<sal_uInt8>( std::min<sal_Int32>(rSpace.nCount, 255) ); + aToken.whitespace.cChar = rSpace.cChar; + } + if (!static_cast<ScTokenArray*>(pArr)->AddRawToken( aToken )) + { + SetError(FormulaError::CodeOverflow); + return false; + } + } + } + + // Short cut for references when reading ODF to speedup things. + if (mnPredetectedReference) + { + OUString aStr( cSymbol); + bool bInvalidExternalNameRange; + if (!ParsePredetectedReference( aStr) && !ParseExternalNamedRange( aStr, bInvalidExternalNameRange )) + { + svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aStr); + maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase()); + maRawToken.NewOpCode( ocBad ); + } + return true; + } + + if ( (cSymbol[0] == '#' || cSymbol[0] == '$') && cSymbol[1] == 0 && + !bAutoCorrect ) + { // special case to speed up broken [$]#REF documents + /* FIXME: ISERROR(#REF!) would be valid and true and the formula to + * be processed as usual. That would need some special treatment, + * also in NextSymbol() because of possible combinations of + * #REF!.#REF!#REF! parts. In case of reading ODF that is all + * handled by IsPredetectedReference(), this case here remains for + * manual/API input. */ + OUString aBad( aFormula.copy( nSrcPos-1 ) ); + const FormulaToken* pBadToken = pArr->AddBad(aBad); + eLastOp = pBadToken ? pBadToken->GetOpCode() : ocNone; + return false; + } + + if( ParseString() ) + return true; + + bool bMayBeFuncName; + bool bAsciiNonAlnum; // operators, separators, ... + if ( cSymbol[0] < 128 ) + { + bMayBeFuncName = rtl::isAsciiAlpha( cSymbol[0] ); + if (!bMayBeFuncName && (cSymbol[0] == '_' && cSymbol[1] == '_') && !utl::ConfigManager::IsFuzzing()) + { + bMayBeFuncName = officecfg::Office::Common::Misc::ExperimentalMode::get(); + } + + bAsciiNonAlnum = !bMayBeFuncName && !rtl::isAsciiDigit( cSymbol[0] ); + } + else + { + OUString aTmpStr( cSymbol[0] ); + bMayBeFuncName = pCharClass->isLetter( aTmpStr, 0 ); + bAsciiNonAlnum = false; + } + + // Within a TableRef anything except an unescaped '[' or ']' is an item + // or a column specifier, do not attempt to recognize any other single + // operator there so even [,] or [+] for a single character column + // specifier works. Note that space between two ocTableRefOpen is not + // supported (Table[ [ColumnSpec]]), not only here. Note also that Table[] + // without any item or column specifier is valid. + if (bAsciiNonAlnum && cSymbol[1] == 0 && (eLastOp != ocTableRefOpen || cSymbol[0] == '[' || cSymbol[0] == ']')) + { + // Shortcut for operators and separators that need no further checks or upper. + if (ParseOpCode( OUString( cSymbol), bInArray )) + return true; + } + + if ( bMayBeFuncName ) + { + // a function name must be followed by a parenthesis + const sal_Unicode* p = aFormula.getStr() + nSrcPos; + while( *p == ' ' ) + p++; + bMayBeFuncName = ( *p == '(' ); + } + + // Italian ARCTAN.2 resulted in #REF! => ParseOpcode() before + // ParseReference(). + + OUString aUpper; + bool bAsciiUpper = false; + +Label_Rewind: + + do + { + const OUString aOrg( cSymbol ); + + // Check for TableRef column specifier first, it may be anything. + if (cSymbol[0] != '#' && !maTableRefs.empty() && maTableRefs.back().mnLevel) + { + if (ParseTableRefColumn( aOrg )) + return true; + // Do not attempt to resolve as any other name. + aUpper = aOrg; // for ocBad + break; // do; create ocBad token or set error. + } + + mbRewind = false; + aUpper.clear(); + bAsciiUpper = false; + + if (bAsciiNonAlnum) + { + bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg); + if (cSymbol[0] == '#') + { + // Check for TableRef item specifiers first. + if (!maTableRefs.empty() && maTableRefs.back().mnLevel == 2) + { + if (ParseTableRefItem( aUpper )) + return true; + } + + // This can be either an error constant ... + if (ParseErrorConstant( aUpper)) + return true; + + // ... or some invalidated reference starting with #REF! + // which is handled after the do loop. + + break; // do; create ocBad token or set error. + } + if (ParseOpCode( aUpper, bInArray )) + return true; + } + + if (bMayBeFuncName) + { + if (aUpper.isEmpty()) + bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg); + if (ParseOpCode( aUpper, bInArray )) + return true; + } + + // Column 'DM' ("Deutsche Mark", German currency) couldn't be + // referred => ParseReference() before ParseValue(). + // Preserve case of file names in external references. + if (ParseReference( aOrg )) + { + if (mbRewind) // Range operator, but no direct reference. + continue; // do; up to range operator. + // If a syntactically correct reference was recognized but invalid + // e.g. because of non-existing sheet name => entire reference + // ocBad to preserve input instead of #REF!.A1 + if (!maRawToken.IsValidReference(rDoc)) + { + aUpper = aOrg; // ensure for ocBad + break; // do; create ocBad token or set error. + } + return true; + } + + if (aUpper.isEmpty()) + bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg); + + // ParseBoolean() before ParseValue() to catch inline bools without the kludge + // for inline arrays. + if (bAllowBooleans && ParseBoolean( aUpper )) + return true; + + if (ParseValue( aUpper )) + return true; + + // User defined names and such do need i18n upper also in ODF. + if (bAsciiUpper || mbCharClassesDiffer) + { + // Use current system locale here because user defined symbols are + // more likely in that localized language than in the formula + // language. This in corner cases needs to continue to work for + // existing documents and environments. + // Do not change bAsciiUpper from here on for the lowercase() call + // below in the ocBad case to use the correct CharClass. + aUpper = ScGlobal::getCharClass().uppercase( aOrg ); + } + + if (ParseNamedRange( aUpper )) + return true; + + // Compiling a named expression during collecting them in import shall + // not match arbitrary names that otherwise if all named expressions + // were present would be recognized as named expression. Such name will + // flag an error below and will be recompiled in a second step later + // with ScRangeData::CompileUnresolvedXML() + if (meExtendedErrorDetection == EXTENDED_ERROR_DETECTION_NAME_NO_BREAK && rDoc.IsImportingXML()) + break; // while + + // Preserve case of file names in external references. + bool bInvalidExternalNameRange; + if (ParseExternalNamedRange( aOrg, bInvalidExternalNameRange )) + return true; + // Preserve case of file names in external references even when range + // is not valid and previous check failed tdf#89330 + if (bInvalidExternalNameRange) + { + // add ocBad but do not lowercase + svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aOrg); + maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase()); + maRawToken.NewOpCode( ocBad ); + return true; + } + if (ParseDBRange( aUpper )) + return true; + // If followed by '(' (with or without space inbetween) it can not be a + // column/row label. Prevent arbitrary content detection. + if (!bMayBeFuncName && ParseColRowName( aUpper )) + return true; + if (bMayBeFuncName && ParseMacro( aUpper )) + return true; + if (bMayBeFuncName && ParseOpCode2( aUpper )) + return true; + + } while (mbRewind); + + // Last chance: it could be a broken invalidated reference that contains + // #REF! (but is not equal to), which we also wrote to ODFF between 2013 + // and 2016 until 5.1.4 + OUString aErrRef( mxSymbols->getSymbol( ocErrRef)); + if (aUpper.indexOf( aErrRef) >= 0 && ParseReference( aUpper, &aErrRef)) + { + if (mbRewind) + goto Label_Rewind; + return true; + } + + if ( meExtendedErrorDetection != EXTENDED_ERROR_DETECTION_NONE ) + { + // set an error + SetError( FormulaError::NoName ); + if (meExtendedErrorDetection == EXTENDED_ERROR_DETECTION_NAME_BREAK) + return false; // end compilation + } + + // Provide single token information and continue. Do not set an error, that + // would prematurely end compilation. Simple unknown names are handled by + // the interpreter. + // Use the same CharClass that was used for uppercase. + aUpper = ((bAsciiUpper || mbCharClassesDiffer) ? ScGlobal::getCharClass() : *pCharClass).lowercase( aUpper ); + svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aUpper); + maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase()); + maRawToken.NewOpCode( ocBad ); + if ( bAutoCorrect ) + AutoCorrectParsedSymbol(); + return true; +} + +void ScCompiler::CreateStringFromXMLTokenArray( OUString& rFormula, OUString& rFormulaNmsp ) +{ + bool bExternal = GetGrammar() == FormulaGrammar::GRAM_EXTERNAL; + sal_uInt16 nExpectedCount = bExternal ? 2 : 1; + OSL_ENSURE( pArr->GetLen() == nExpectedCount, "ScCompiler::CreateStringFromXMLTokenArray - wrong number of tokens" ); + if( pArr->GetLen() == nExpectedCount ) + { + FormulaToken** ppTokens = pArr->GetArray(); + // string tokens expected, GetString() will assert if token type is wrong + rFormula = ppTokens[0]->GetString().getString(); + if( bExternal ) + rFormulaNmsp = ppTokens[1]->GetString().getString(); + } +} + +namespace { + +class ExternalFileInserter +{ + ScAddress maPos; + ScExternalRefManager& mrRefMgr; +public: + ExternalFileInserter(const ScAddress& rPos, ScExternalRefManager& rRefMgr) : + maPos(rPos), mrRefMgr(rRefMgr) {} + + void operator() (sal_uInt16 nFileId) const + { + mrRefMgr.insertRefCell(nFileId, maPos); + } +}; + +} + +std::unique_ptr<ScTokenArray> ScCompiler::CompileString( const OUString& rFormula ) +{ + OSL_ENSURE( meGrammar != FormulaGrammar::GRAM_EXTERNAL, "ScCompiler::CompileString - unexpected grammar GRAM_EXTERNAL" ); + if( meGrammar == FormulaGrammar::GRAM_EXTERNAL ) + SetGrammar( FormulaGrammar::GRAM_PODF ); + + ScTokenArray aArr(rDoc); + pArr = &aArr; + maArrIterator = FormulaTokenArrayPlainIterator(*pArr); + aFormula = comphelper::string::strip(rFormula, ' '); + + nSrcPos = 0; + bCorrected = false; + if ( bAutoCorrect ) + { + aCorrectedFormula.clear(); + aCorrectedSymbol.clear(); + } + sal_uInt8 nForced = 0; // ==formula forces recalc even if cell is not visible + if( nSrcPos < aFormula.getLength() && aFormula[nSrcPos] == '=' ) + { + nSrcPos++; + nForced++; + if ( bAutoCorrect ) + aCorrectedFormula += "="; + } + if( nSrcPos < aFormula.getLength() && aFormula[nSrcPos] == '=' ) + { + nSrcPos++; + nForced++; + if ( bAutoCorrect ) + aCorrectedFormula += "="; + } + struct FunctionStack + { + OpCode eOp; + short nSep; + }; + // FunctionStack only used if PODF or OOXML! + bool bPODF = FormulaGrammar::isPODF( meGrammar); + bool bOOXML = FormulaGrammar::isOOXML( meGrammar); + bool bUseFunctionStack = (bPODF || bOOXML); + const size_t nAlloc = 512; + FunctionStack aFuncs[ nAlloc ]; + FunctionStack* pFunctionStack = (bUseFunctionStack && o3tl::make_unsigned(rFormula.getLength()) > nAlloc ? + new FunctionStack[rFormula.getLength()] : &aFuncs[0]); + pFunctionStack[0].eOp = ocNone; + pFunctionStack[0].nSep = 0; + size_t nFunction = 0; + short nBrackets = 0; + bool bInArray = false; + eLastOp = ocOpen; + while( NextNewToken( bInArray ) ) + { + const OpCode eOp = maRawToken.GetOpCode(); + if (eOp == ocSkip) + continue; + + switch (eOp) + { + case ocOpen: + { + ++nBrackets; + if (bUseFunctionStack) + { + ++nFunction; + pFunctionStack[ nFunction ].eOp = eLastOp; + pFunctionStack[ nFunction ].nSep = 0; + } + } + break; + case ocClose: + { + if( !nBrackets ) + { + SetError( FormulaError::PairExpected ); + if ( bAutoCorrect ) + { + bCorrected = true; + aCorrectedSymbol.clear(); + } + } + else + nBrackets--; + if (bUseFunctionStack && nFunction) + --nFunction; + } + break; + case ocSep: + { + if (bUseFunctionStack) + ++pFunctionStack[ nFunction ].nSep; + } + break; + case ocArrayOpen: + { + if( bInArray ) + SetError( FormulaError::NestedArray ); + else + bInArray = true; + // Don't count following column separator as parameter separator. + if (bUseFunctionStack) + { + ++nFunction; + pFunctionStack[ nFunction ].eOp = eOp; + pFunctionStack[ nFunction ].nSep = 0; + } + } + break; + case ocArrayClose: + { + if( bInArray ) + { + bInArray = false; + } + else + { + SetError( FormulaError::PairExpected ); + if ( bAutoCorrect ) + { + bCorrected = true; + aCorrectedSymbol.clear(); + } + } + if (bUseFunctionStack && nFunction) + --nFunction; + } + break; + case ocTableRefOpen: + { + // Don't count following item separator as parameter separator. + if (bUseFunctionStack) + { + ++nFunction; + pFunctionStack[ nFunction ].eOp = eOp; + pFunctionStack[ nFunction ].nSep = 0; + } + } + break; + case ocTableRefClose: + { + if (bUseFunctionStack && nFunction) + --nFunction; + } + break; + case ocColRowName: + case ocColRowNameAuto: + // The current implementation of column / row labels doesn't + // function correctly in grouped cells. + aArr.SetShareable(false); + break; + default: + break; + } + if ((eLastOp != ocOpen || eOp != ocClose) && + (eLastOp == ocOpen || + eLastOp == ocSep || + eLastOp == ocArrayRowSep || + eLastOp == ocArrayColSep || + eLastOp == ocArrayOpen) && + (eOp == ocSep || + eOp == ocClose || + eOp == ocArrayRowSep || + eOp == ocArrayColSep || + eOp == ocArrayClose)) + { + // TODO: should we check for known functions with optional empty + // args so the correction dialog can do better? + if ( !static_cast<ScTokenArray*>(pArr)->Add( new FormulaMissingToken ) ) + { + SetError(FormulaError::CodeOverflow); break; + } + } + if (bOOXML) + { + // Append a parameter for WEEKNUM, all 1.0 + // Function is already closed, parameter count is nSep+1 + size_t nFunc = nFunction + 1; + if (eOp == ocClose && + (pFunctionStack[ nFunc ].eOp == ocWeek && // 2nd week start + pFunctionStack[ nFunc ].nSep == 0)) + { + if ( !static_cast<ScTokenArray*>(pArr)->Add( new FormulaToken( svSep, ocSep)) || + !static_cast<ScTokenArray*>(pArr)->Add( new FormulaDoubleToken( 1.0))) + { + SetError(FormulaError::CodeOverflow); break; + } + } + } + else if (bPODF) + { + /* TODO: for now this is the only PODF adapter. If there were more, + * factor this out. */ + // Insert ADDRESS() new empty parameter 4 if there is a 4th, now to be 5th. + if (eOp == ocSep && + pFunctionStack[ nFunction ].eOp == ocAddress && + pFunctionStack[ nFunction ].nSep == 3) + { + if ( !static_cast<ScTokenArray*>(pArr)->Add( new FormulaToken( svSep, ocSep)) || + !static_cast<ScTokenArray*>(pArr)->Add( new FormulaDoubleToken( 1.0))) + { + SetError(FormulaError::CodeOverflow); break; + } + ++pFunctionStack[ nFunction ].nSep; + } + } + FormulaToken* pNewToken = static_cast<ScTokenArray*>(pArr)->Add( maRawToken.CreateToken(rDoc.GetSheetLimits())); + if (!pNewToken && eOp == ocArrayClose && pArr->OpCodeBefore( pArr->GetLen()) == ocArrayClose) + { + // Nested inline array or non-value/non-string in array. The + // original tokens are still in the ScTokenArray and not merged + // into an ScMatrixToken. Set error but keep on tokenizing. + SetError( FormulaError::BadArrayContent); + } + else if (!pNewToken) + { + SetError(FormulaError::CodeOverflow); + break; + } + else if (eLastOp == ocRange && pNewToken->GetOpCode() == ocPush && pNewToken->GetType() == svSingleRef) + { + static_cast<ScTokenArray*>(pArr)->MergeRangeReference( aPos); + } + else if (eLastOp == ocDBArea && pNewToken->GetOpCode() == ocTableRefOpen) + { + sal_uInt16 nIdx = pArr->GetLen() - 1; + const FormulaToken* pPrev = pArr->PeekPrev( nIdx); + if (pPrev && pPrev->GetOpCode() == ocDBArea) + { + ScTableRefToken* pTableRefToken = new ScTableRefToken( pPrev->GetIndex(), ScTableRefToken::TABLE); + maTableRefs.emplace_back( pTableRefToken); + // pPrev may be dead hereafter. + static_cast<ScTokenArray*>(pArr)->ReplaceToken( nIdx, pTableRefToken, + FormulaTokenArray::ReplaceMode::CODE_ONLY); + } + } + switch (eOp) + { + case ocTableRefOpen: + SAL_WARN_IF( maTableRefs.empty(), "sc.core", "ocTableRefOpen without TableRefEntry"); + if (maTableRefs.empty()) + SetError(FormulaError::Pair); + else + ++maTableRefs.back().mnLevel; + break; + case ocTableRefClose: + SAL_WARN_IF( maTableRefs.empty(), "sc.core", "ocTableRefClose without TableRefEntry"); + if (maTableRefs.empty()) + SetError(FormulaError::Pair); + else + { + if (--maTableRefs.back().mnLevel == 0) + maTableRefs.pop_back(); + } + break; + default: + break; + } + eLastOp = maRawToken.GetOpCode(); + if ( bAutoCorrect ) + aCorrectedFormula += aCorrectedSymbol; + } + if ( mbCloseBrackets ) + { + if( bInArray ) + { + FormulaByteToken aToken( ocArrayClose ); + if( !pArr->AddToken( aToken ) ) + { + SetError(FormulaError::CodeOverflow); + } + else if ( bAutoCorrect ) + aCorrectedFormula += mxSymbols->getSymbol(ocArrayClose); + } + + if (nBrackets) + { + FormulaToken aToken( svSep, ocClose ); + while( nBrackets-- ) + { + if( !pArr->AddToken( aToken ) ) + { + SetError(FormulaError::CodeOverflow); + break; // while + } + if ( bAutoCorrect ) + aCorrectedFormula += mxSymbols->getSymbol(ocClose); + } + } + } + if ( nForced >= 2 ) + pArr->SetRecalcModeForced(); + + if (pFunctionStack != &aFuncs[0]) + delete [] pFunctionStack; + + // remember pArr, in case a subsequent CompileTokenArray() is executed. + std::unique_ptr<ScTokenArray> pNew(new ScTokenArray( aArr )); + pNew->GenHash(); + // coverity[escape : FALSE] - ownership of pNew is retained by caller, so pArr remains valid + pArr = pNew.get(); + maArrIterator = FormulaTokenArrayPlainIterator(*pArr); + + if (!maExternalFiles.empty()) + { + // Remove duplicates, and register all external files found in this cell. + std::sort(maExternalFiles.begin(), maExternalFiles.end()); + std::vector<sal_uInt16>::iterator itEnd = std::unique(maExternalFiles.begin(), maExternalFiles.end()); + std::for_each(maExternalFiles.begin(), itEnd, ExternalFileInserter(aPos, *rDoc.GetExternalRefManager())); + maExternalFiles.erase(itEnd, maExternalFiles.end()); + } + + return pNew; +} + +std::unique_ptr<ScTokenArray> ScCompiler::CompileString( const OUString& rFormula, const OUString& rFormulaNmsp ) +{ + OSL_ENSURE( (GetGrammar() == FormulaGrammar::GRAM_EXTERNAL) || rFormulaNmsp.isEmpty(), + "ScCompiler::CompileString - unexpected formula namespace for internal grammar" ); + if( GetGrammar() == FormulaGrammar::GRAM_EXTERNAL ) try + { + ScFormulaParserPool& rParserPool = rDoc.GetFormulaParserPool(); + uno::Reference< sheet::XFormulaParser > xParser( rParserPool.getFormulaParser( rFormulaNmsp ), uno::UNO_SET_THROW ); + table::CellAddress aReferencePos; + ScUnoConversion::FillApiAddress( aReferencePos, aPos ); + uno::Sequence< sheet::FormulaToken > aTokenSeq = xParser->parseFormula( rFormula, aReferencePos ); + ScTokenArray aTokenArray(rDoc); + if( ScTokenConversion::ConvertToTokenArray( rDoc, aTokenArray, aTokenSeq ) ) + { + // remember pArr, in case a subsequent CompileTokenArray() is executed. + std::unique_ptr<ScTokenArray> pNew(new ScTokenArray( aTokenArray )); + // coverity[escape : FALSE] - ownership of pNew is retained by caller, so pArr remains valid + pArr = pNew.get(); + maArrIterator = FormulaTokenArrayPlainIterator(*pArr); + return pNew; + } + } + catch( uno::Exception& ) + { + } + // no success - fallback to some internal grammar and hope the best + return CompileString( rFormula ); +} + +ScRangeData* ScCompiler::GetRangeData( const FormulaToken& rToken ) const +{ + return rDoc.FindRangeNameBySheetAndIndex( rToken.GetSheet(), rToken.GetIndex()); +} + +bool ScCompiler::HandleRange() +{ + ScTokenArray* pNew; + const ScRangeData* pRangeData = GetRangeData( *mpToken); + if (pRangeData) + { + FormulaError nErr = pRangeData->GetErrCode(); + if( nErr != FormulaError::NONE ) + SetError( nErr ); + else if (mbJumpCommandReorder) + { + // put named formula into parentheses. + // But only if there aren't any yet, parenthetical + // ocSep doesn't work, e.g. SUM((...;...)) + // or if not directly between ocSep/parenthesis, + // e.g. SUM(...;(...;...)) no, SUM(...;(...)*3) yes, + // in short: if it isn't a self-contained expression. + FormulaToken* p1 = maArrIterator.PeekPrevNoSpaces(); + FormulaToken* p2 = maArrIterator.PeekNextNoSpaces(); + OpCode eOp1 = (p1 ? p1->GetOpCode() : ocSep); + OpCode eOp2 = (p2 ? p2->GetOpCode() : ocSep); + bool bBorder1 = (eOp1 == ocSep || eOp1 == ocOpen); + bool bBorder2 = (eOp2 == ocSep || eOp2 == ocClose); + bool bAddPair = !(bBorder1 && bBorder2); + if ( bAddPair ) + { + pNew = new ScTokenArray(rDoc); + pNew->AddOpCode( ocClose ); + PushTokenArray( pNew, true ); + } + pNew = pRangeData->GetCode()->Clone().release(); + pNew->SetFromRangeName( true ); + PushTokenArray( pNew, true ); + if( pRangeData->HasReferences() ) + { + // Relative sheet references in sheet-local named expressions + // shall still point to the same sheet as if used on the + // original sheet, not shifted to the current position where + // they are used. + SCTAB nSheetTab = mpToken->GetSheet(); + if (nSheetTab >= 0 && nSheetTab != aPos.Tab()) + AdjustSheetLocalNameRelReferences( nSheetTab - aPos.Tab()); + + SetRelNameReference(); + MoveRelWrap(); + } + maArrIterator.Reset(); + if ( bAddPair ) + { + pNew = new ScTokenArray(rDoc); + pNew->AddOpCode( ocOpen ); + PushTokenArray( pNew, true ); + } + return GetToken(); + } + } + else + { + // No ScRangeData for an already compiled token can happen in BIFF .xls + // import if the original range is not present in the document. + pNew = new ScTokenArray(rDoc); + pNew->Add( new FormulaErrorToken( FormulaError::NoName)); + PushTokenArray( pNew, true ); + return GetToken(); + } + return true; +} + +bool ScCompiler::HandleExternalReference(const FormulaToken& _aToken) +{ + // Handle external range names. + switch (_aToken.GetType()) + { + case svExternalSingleRef: + case svExternalDoubleRef: + break; + case svExternalName: + { + ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); + const OUString* pFile = pRefMgr->getExternalFileName(_aToken.GetIndex()); + if (!pFile) + { + SetError(FormulaError::NoName); + return true; + } + + OUString aName = _aToken.GetString().getString(); + ScExternalRefCache::TokenArrayRef xNew = pRefMgr->getRangeNameTokens( + _aToken.GetIndex(), aName, &aPos); + + if (!xNew) + { + SetError(FormulaError::NoName); + return true; + } + + ScTokenArray* pNew = xNew->Clone().release(); + PushTokenArray( pNew, true); + if (FormulaTokenArrayPlainIterator(*pNew).GetNextReference() != nullptr) + { + SetRelNameReference(); + MoveRelWrap(); + } + maArrIterator.Reset(); + return GetToken(); + } + default: + OSL_FAIL("Wrong type for external reference!"); + return false; + } + return true; +} + +void ScCompiler::AdjustSheetLocalNameRelReferences( SCTAB nDelta ) +{ + for ( auto t: pArr->References() ) + { + ScSingleRefData& rRef1 = *t->GetSingleRef(); + if (rRef1.IsTabRel()) + rRef1.IncTab( nDelta); + if ( t->GetType() == svDoubleRef ) + { + ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2; + if (rRef2.IsTabRel()) + rRef2.IncTab( nDelta); + } + } +} + +// reference of named range with relative references + +void ScCompiler::SetRelNameReference() +{ + for ( auto t: pArr->References() ) + { + ScSingleRefData& rRef1 = *t->GetSingleRef(); + if ( rRef1.IsColRel() || rRef1.IsRowRel() || rRef1.IsTabRel() ) + rRef1.SetRelName( true ); + if ( t->GetType() == svDoubleRef ) + { + ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2; + if ( rRef2.IsColRel() || rRef2.IsRowRel() || rRef2.IsTabRel() ) + rRef2.SetRelName( true ); + } + } +} + +// Wrap-adjust relative references of a RangeName to current position, +// don't call for other token arrays! +void ScCompiler::MoveRelWrap() +{ + for ( auto t: pArr->References() ) + { + if ( t->GetType() == svSingleRef || t->GetType() == svExternalSingleRef ) + ScRefUpdate::MoveRelWrap( rDoc, aPos, rDoc.MaxCol(), rDoc.MaxRow(), SingleDoubleRefModifier( *t->GetSingleRef() ).Ref() ); + else + ScRefUpdate::MoveRelWrap( rDoc, aPos, rDoc.MaxCol(), rDoc.MaxRow(), *t->GetDoubleRef() ); + } +} + +// Wrap-adjust relative references of a RangeName to current position, +// don't call for other token arrays! +void ScCompiler::MoveRelWrap( const ScTokenArray& rArr, const ScDocument& rDoc, const ScAddress& rPos, + SCCOL nMaxCol, SCROW nMaxRow ) +{ + for ( auto t: rArr.References() ) + { + if ( t->GetType() == svSingleRef || t->GetType() == svExternalSingleRef ) + ScRefUpdate::MoveRelWrap( rDoc, rPos, nMaxCol, nMaxRow, SingleDoubleRefModifier( *t->GetSingleRef() ).Ref() ); + else + ScRefUpdate::MoveRelWrap( rDoc, rPos, nMaxCol, nMaxRow, *t->GetDoubleRef() ); + } +} + +bool ScCompiler::IsCharFlagAllConventions( + OUString const & rStr, sal_Int32 nPos, ScCharFlags nFlags ) +{ + sal_Unicode c = rStr[ nPos ]; + sal_Unicode cLast = nPos > 0 ? rStr[ nPos-1 ] : 0; + if (c < 128) + { + for ( int nConv = formula::FormulaGrammar::CONV_UNSPECIFIED; + ++nConv < formula::FormulaGrammar::CONV_LAST; ) + { + if (pConventions[nConv] && + ((pConventions[nConv]->getCharTableFlags(c, cLast) & nFlags) != nFlags)) + return false; + // convention not known => assume valid + } + return true; + } + else + return ScGlobal::getCharClass().isLetterNumeric( rStr, nPos ); +} + +void ScCompiler::CreateStringFromExternal( OUStringBuffer& rBuffer, const FormulaToken* pTokenP ) const +{ + const FormulaToken* t = pTokenP; + sal_uInt16 nFileId = t->GetIndex(); + ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); + sal_uInt16 nUsedFileId = pRefMgr->convertFileIdToUsedFileId(nFileId); + const OUString* pFileName = pRefMgr->getExternalFileName(nFileId); + if (!pFileName) + return; + + switch (t->GetType()) + { + case svExternalName: + rBuffer.append(pConv->makeExternalNameStr( nFileId, *pFileName, t->GetString().getString())); + break; + case svExternalSingleRef: + pConv->makeExternalRefStr(rDoc.GetSheetLimits(), + rBuffer, GetPos(), nUsedFileId, *pFileName, t->GetString().getString(), + *t->GetSingleRef()); + break; + case svExternalDoubleRef: + { + vector<OUString> aTabNames; + pRefMgr->getAllCachedTableNames(nFileId, aTabNames); + // No sheet names is a valid case if external sheets were not + // cached in this document and external document is not reachable, + // else not and worth to be investigated. + SAL_WARN_IF( aTabNames.empty(), "sc.core", "wrecked cache of external document? '" << + *pFileName << "' '" << t->GetString().getString() << "'"); + + pConv->makeExternalRefStr( + rDoc.GetSheetLimits(), rBuffer, GetPos(), nUsedFileId, *pFileName, aTabNames, t->GetString().getString(), + *t->GetDoubleRef()); + } + break; + default: + // warning, not error, otherwise we may end up with a never + // ending message box loop if this was the cursor cell to be redrawn. + OSL_FAIL("ScCompiler::CreateStringFromToken: unknown type of ocExternalRef"); + } +} + +void ScCompiler::CreateStringFromMatrix( OUStringBuffer& rBuffer, const FormulaToken* pTokenP ) const +{ + const ScMatrix* pMatrix = pTokenP->GetMatrix(); + SCSIZE nC, nMaxC, nR, nMaxR; + + pMatrix->GetDimensions( nMaxC, nMaxR); + + rBuffer.append( mxSymbols->getSymbol(ocArrayOpen) ); + for( nR = 0 ; nR < nMaxR ; nR++) + { + if( nR > 0) + { + rBuffer.append( mxSymbols->getSymbol(ocArrayRowSep) ); + } + + for( nC = 0 ; nC < nMaxC ; nC++) + { + if( nC > 0) + { + rBuffer.append( mxSymbols->getSymbol(ocArrayColSep) ); + } + + if( pMatrix->IsValue( nC, nR ) ) + { + if (pMatrix->IsBoolean(nC, nR)) + AppendBoolean(rBuffer, pMatrix->GetDouble(nC, nR) != 0.0); + else + { + FormulaError nErr = pMatrix->GetError(nC, nR); + if (nErr != FormulaError::NONE) + rBuffer.append(ScGlobal::GetErrorString(nErr)); + else + AppendDouble(rBuffer, pMatrix->GetDouble(nC, nR)); + } + } + else if( pMatrix->IsEmpty( nC, nR ) ) + ; + else if( pMatrix->IsStringOrEmpty( nC, nR ) ) + AppendString( rBuffer, pMatrix->GetString(nC, nR).getString() ); + } + } + rBuffer.append( mxSymbols->getSymbol(ocArrayClose) ); +} + +namespace { +void escapeTableRefColumnSpecifier( OUString& rStr ) +{ + const sal_Int32 n = rStr.getLength(); + OUStringBuffer aBuf( n * 2 ); + const sal_Unicode* p = rStr.getStr(); + const sal_Unicode* const pStop = p + n; + for ( ; p < pStop; ++p) + { + const sal_Unicode c = *p; + switch (c) + { + case '\'': + case '[': + case '#': + case ']': + aBuf.append( '\'' ); + break; + default: + ; // nothing + } + aBuf.append( c ); + } + rStr = aBuf.makeStringAndClear(); +} +} + +void ScCompiler::CreateStringFromSingleRef( OUStringBuffer& rBuffer, const FormulaToken* _pTokenP ) const +{ + const FormulaToken* p; + OUString aErrRef = GetCurrentOpCodeMap()->getSymbol(ocErrRef); + const OpCode eOp = _pTokenP->GetOpCode(); + const ScSingleRefData& rRef = *_pTokenP->GetSingleRef(); + ScComplexRefData aRef; + aRef.Ref1 = aRef.Ref2 = rRef; + if ( eOp == ocColRowName ) + { + ScAddress aAbs = rRef.toAbs(rDoc, aPos); + if (rDoc.HasStringData(aAbs.Col(), aAbs.Row(), aAbs.Tab())) + { + OUString aStr = rDoc.GetString(aAbs, mpInterpreterContext); + // Enquote to SingleQuoted. + aStr = aStr.replaceAll(u"'", u"''"); + rBuffer.append('\''); + rBuffer.append(aStr); + rBuffer.append('\''); + } + else + { + rBuffer.append(ScCompiler::GetNativeSymbol(ocErrName)); + pConv->makeRefStr(rDoc.GetSheetLimits(), rBuffer, meGrammar, aPos, aErrRef, + GetSetupTabNames(), aRef, true, (pArr && pArr->IsFromRangeName())); + } + } + else if (pArr && (p = maArrIterator.PeekPrevNoSpaces()) && p->GetOpCode() == ocTableRefOpen) + { + OUString aStr; + ScAddress aAbs = rRef.toAbs(rDoc, aPos); + const ScDBData* pData = rDoc.GetDBAtCursor( aAbs.Col(), aAbs.Row(), aAbs.Tab(), ScDBDataPortion::AREA); + SAL_WARN_IF( !pData, "sc.core", "ScCompiler::CreateStringFromSingleRef - TableRef without ScDBData: " << + aAbs.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDoc)); + if (pData) + aStr = pData->GetTableColumnName( aAbs.Col()); + if (aStr.isEmpty()) + { + if (pData && pData->HasHeader()) + { + SAL_WARN("sc.core", "ScCompiler::CreateStringFromSingleRef - TableRef falling back to cell: " << + aAbs.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDoc)); + aStr = rDoc.GetString(aAbs, mpInterpreterContext); + } + else + { + SAL_WARN("sc.core", "ScCompiler::CreateStringFromSingleRef - TableRef of empty header-less: " << + aAbs.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDoc)); + aStr = aErrRef; + } + } + escapeTableRefColumnSpecifier( aStr); + rBuffer.append(aStr); + } + else + pConv->makeRefStr(rDoc.GetSheetLimits(), rBuffer, meGrammar, aPos, aErrRef, + GetSetupTabNames(), aRef, true, (pArr && pArr->IsFromRangeName())); +} + +void ScCompiler::CreateStringFromDoubleRef( OUStringBuffer& rBuffer, const FormulaToken* _pTokenP ) const +{ + OUString aErrRef = GetCurrentOpCodeMap()->getSymbol(ocErrRef); + pConv->makeRefStr(rDoc.GetSheetLimits(), rBuffer, meGrammar, aPos, aErrRef, GetSetupTabNames(), + *_pTokenP->GetDoubleRef(), false, (pArr && pArr->IsFromRangeName())); +} + +void ScCompiler::CreateStringFromIndex( OUStringBuffer& rBuffer, const FormulaToken* _pTokenP ) const +{ + const OpCode eOp = _pTokenP->GetOpCode(); + OUStringBuffer aBuffer; + switch ( eOp ) + { + case ocName: + { + const ScRangeData* pData = GetRangeData( *_pTokenP); + if (pData) + { + SCTAB nTab = _pTokenP->GetSheet(); + if (nTab >= 0 && (nTab != aPos.Tab() || mbRefConventionChartOOXML)) + { + // Sheet-local on other sheet. + OUString aName; + if (rDoc.GetName( nTab, aName)) + { + ScCompiler::CheckTabQuotes( aName, pConv->meConv); + aBuffer.append( aName); + } + else + aBuffer.append(ScCompiler::GetNativeSymbol(ocErrName)); + aBuffer.append( pConv->getSpecialSymbol( ScCompiler::Convention::SHEET_SEPARATOR)); + } + else if (mbRefConventionChartOOXML) + { + aBuffer.append("[0]" + + OUStringChar(pConv->getSpecialSymbol(ScCompiler::Convention::SHEET_SEPARATOR))); + } + aBuffer.append(pData->GetName()); + } + } + break; + case ocDBArea: + { + const ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex(_pTokenP->GetIndex()); + if (pDBData) + aBuffer.append(pDBData->GetName()); + } + break; + case ocTableRef: + { + if (NeedsTableRefTransformation()) + { + // Write the resulting reference if TableRef is not supported. + const ScTableRefToken* pTR = dynamic_cast<const ScTableRefToken*>(_pTokenP); + if (!pTR) + AppendErrorConstant( aBuffer, FormulaError::NoCode); + else + { + const FormulaToken* pRef = pTR->GetAreaRefRPN(); + if (!pRef) + AppendErrorConstant( aBuffer, FormulaError::NoCode); + else + { + switch (pRef->GetType()) + { + case svSingleRef: + CreateStringFromSingleRef( aBuffer, pRef); + break; + case svDoubleRef: + CreateStringFromDoubleRef( aBuffer, pRef); + break; + case svError: + AppendErrorConstant( aBuffer, pRef->GetError()); + break; + default: + AppendErrorConstant( aBuffer, FormulaError::NoCode); + } + } + } + } + else + { + const ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex(_pTokenP->GetIndex()); + if (pDBData) + aBuffer.append(pDBData->GetName()); + } + } + break; + default: + ; // nothing + } + if ( !aBuffer.isEmpty() ) + rBuffer.append(aBuffer); + else + rBuffer.append(ScCompiler::GetNativeSymbol(ocErrName)); +} + +void ScCompiler::LocalizeString( OUString& rName ) const +{ + ScGlobal::GetAddInCollection()->LocalizeString( rName ); +} + +FormulaTokenRef ScCompiler::ExtendRangeReference( FormulaToken & rTok1, FormulaToken & rTok2 ) +{ + return extendRangeReference( rDoc.GetSheetLimits(), rTok1, rTok2, aPos, true/*bReuseDoubleRef*/ ); +} + +void ScCompiler::fillAddInToken(::std::vector< css::sheet::FormulaOpCodeMapEntry >& _rVec,bool _bIsEnglish) const +{ + // All known AddIn functions. + sheet::FormulaOpCodeMapEntry aEntry; + aEntry.Token.OpCode = ocExternal; + + const LanguageTag aEnglishLanguageTag(LANGUAGE_ENGLISH_US); + ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection(); + const tools::Long nCount = pColl->GetFuncCount(); + for (tools::Long i=0; i < nCount; ++i) + { + const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i); + if (pFuncData) + { + if ( _bIsEnglish ) + { + // This is used with OOXML import, so GetExcelName() is really + // wanted here until we'll have a parameter to differentiate + // from the general css::sheet::XFormulaOpCodeMapper case and + // use pFuncData->GetUpperEnglish(). + OUString aName; + if (pFuncData->GetExcelName( aEnglishLanguageTag, aName)) + aEntry.Name = aName; + else + aEntry.Name = pFuncData->GetUpperName(); + } + else + aEntry.Name = pFuncData->GetUpperLocal(); + aEntry.Token.Data <<= pFuncData->GetOriginalName(); + _rVec.push_back( aEntry); + } + } + // FIXME: what about those old non-UNO AddIns? +} + +bool ScCompiler::HandleColRowName() +{ + ScSingleRefData& rRef = *mpToken->GetSingleRef(); + const ScAddress aAbs = rRef.toAbs(rDoc, aPos); + if (!rDoc.ValidAddress(aAbs)) + { + SetError( FormulaError::NoRef ); + return true; + } + SCCOL nCol = aAbs.Col(); + SCROW nRow = aAbs.Row(); + SCTAB nTab = aAbs.Tab(); + bool bColName = rRef.IsColRel(); + SCCOL nMyCol = aPos.Col(); + SCROW nMyRow = aPos.Row(); + bool bInList = false; + bool bValidName = false; + ScRangePairList* pRL = (bColName ? + rDoc.GetColNameRanges() : rDoc.GetRowNameRanges()); + ScRange aRange; + for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i ) + { + const ScRangePair & rR = (*pRL)[i]; + if ( rR.GetRange(0).Contains( aAbs ) ) + { + bInList = bValidName = true; + aRange = rR.GetRange(1); + if ( bColName ) + { + aRange.aStart.SetCol( nCol ); + aRange.aEnd.SetCol( nCol ); + } + else + { + aRange.aStart.SetRow( nRow ); + aRange.aEnd.SetRow( nRow ); + } + break; // for + } + } + if ( !bInList && rDoc.GetDocOptions().IsLookUpColRowNames() ) + { // automagically or created by copying and NamePos isn't in list + ScRefCellValue aCell(rDoc, aAbs); + bool bString = aCell.hasString(); + if (!bString && aCell.isEmpty()) + bString = true; // empty cell is ok + if ( bString ) + { // corresponds with ScInterpreter::ScColRowNameAuto() + bValidName = true; + if ( bColName ) + { // ColName + SCROW nStartRow = nRow + 1; + if ( nStartRow > rDoc.MaxRow() ) + nStartRow = rDoc.MaxRow(); + SCROW nMaxRow = rDoc.MaxRow(); + if ( nMyCol == nCol ) + { // formula cell in same column + if ( nMyRow == nStartRow ) + { // take remainder under name cell + nStartRow++; + if ( nStartRow > rDoc.MaxRow() ) + nStartRow = rDoc.MaxRow(); + } + else if ( nMyRow > nStartRow ) + { // from name cell down to formula cell + nMaxRow = nMyRow - 1; + } + } + for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i ) + { // next defined ColNameRange below limits row + const ScRangePair & rR = (*pRL)[i]; + const ScRange& rRange = rR.GetRange(1); + if ( rRange.aStart.Col() <= nCol && nCol <= rRange.aEnd.Col() ) + { // identical column range + SCROW nTmp = rRange.aStart.Row(); + if ( nStartRow < nTmp && nTmp <= nMaxRow ) + nMaxRow = nTmp - 1; + } + } + aRange.aStart.Set( nCol, nStartRow, nTab ); + aRange.aEnd.Set( nCol, nMaxRow, nTab ); + } + else + { // RowName + SCCOL nStartCol = nCol + 1; + if ( nStartCol > rDoc.MaxCol() ) + nStartCol = rDoc.MaxCol(); + SCCOL nMaxCol = rDoc.MaxCol(); + if ( nMyRow == nRow ) + { // formula cell in same row + if ( nMyCol == nStartCol ) + { // take remainder right from name cell + nStartCol++; + if ( nStartCol > rDoc.MaxCol() ) + nStartCol = rDoc.MaxCol(); + } + else if ( nMyCol > nStartCol ) + { // from name cell right to formula cell + nMaxCol = nMyCol - 1; + } + } + for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i ) + { // next defined RowNameRange to the right limits column + const ScRangePair & rR = (*pRL)[i]; + const ScRange& rRange = rR.GetRange(1); + if ( rRange.aStart.Row() <= nRow && nRow <= rRange.aEnd.Row() ) + { // identical row range + SCCOL nTmp = rRange.aStart.Col(); + if ( nStartCol < nTmp && nTmp <= nMaxCol ) + nMaxCol = nTmp - 1; + } + } + aRange.aStart.Set( nStartCol, nRow, nTab ); + aRange.aEnd.Set( nMaxCol, nRow, nTab ); + } + } + } + if ( bValidName ) + { + // And now the magic to distinguish between a range and a single + // cell thereof, which is picked position-dependent of the formula + // cell. If a direct neighbor is a binary operator (ocAdd, ...) a + // SingleRef matching the column/row of the formula cell is + // generated. A ocColRowName or ocIntersect as a neighbor results + // in a range. Special case: if label is valid for a single cell, a + // position independent SingleRef is generated. + bool bSingle = (aRange.aStart == aRange.aEnd); + bool bFound; + if ( bSingle ) + bFound = true; + else + { + FormulaToken* p1 = maArrIterator.PeekPrevNoSpaces(); + FormulaToken* p2 = maArrIterator.PeekNextNoSpaces(); + // begin/end of a formula => single + OpCode eOp1 = p1 ? p1->GetOpCode() : ocAdd; + OpCode eOp2 = p2 ? p2->GetOpCode() : ocAdd; + if ( eOp1 != ocColRowName && eOp1 != ocIntersect + && eOp2 != ocColRowName && eOp2 != ocIntersect ) + { + if ( (SC_OPCODE_START_BIN_OP <= eOp1 && eOp1 < SC_OPCODE_STOP_BIN_OP) || + (SC_OPCODE_START_BIN_OP <= eOp2 && eOp2 < SC_OPCODE_STOP_BIN_OP)) + bSingle = true; + } + if ( bSingle ) + { // column and/or row must match range + if ( bColName ) + { + bFound = (aRange.aStart.Row() <= nMyRow + && nMyRow <= aRange.aEnd.Row()); + if ( bFound ) + aRange.aStart.SetRow( nMyRow ); + } + else + { + bFound = (aRange.aStart.Col() <= nMyCol + && nMyCol <= aRange.aEnd.Col()); + if ( bFound ) + aRange.aStart.SetCol( nMyCol ); + } + } + else + bFound = true; + } + if ( !bFound ) + SetError(FormulaError::NoRef); + else if (mbJumpCommandReorder) + { + ScTokenArray* pNew = new ScTokenArray(rDoc); + if ( bSingle ) + { + ScSingleRefData aRefData; + aRefData.InitAddress( aRange.aStart ); + if ( bColName ) + aRefData.SetColRel( true ); + else + aRefData.SetRowRel( true ); + aRefData.SetAddress(rDoc.GetSheetLimits(), aRange.aStart, aPos); + pNew->AddSingleReference( aRefData ); + } + else + { + ScComplexRefData aRefData; + aRefData.InitRange( aRange ); + if ( bColName ) + { + aRefData.Ref1.SetColRel( true ); + aRefData.Ref2.SetColRel( true ); + } + else + { + aRefData.Ref1.SetRowRel( true ); + aRefData.Ref2.SetRowRel( true ); + } + aRefData.SetRange(rDoc.GetSheetLimits(), aRange, aPos); + if ( bInList ) + pNew->AddDoubleReference( aRefData ); + else + { // automagically + pNew->Add( new ScDoubleRefToken( rDoc.GetSheetLimits(), aRefData, ocColRowNameAuto ) ); + } + } + PushTokenArray( pNew, true ); + return GetToken(); + } + } + else + SetError(FormulaError::NoName); + return true; +} + +bool ScCompiler::HandleDbData() +{ + ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex(mpToken->GetIndex()); + if ( !pDBData ) + SetError(FormulaError::NoName); + else if (mbJumpCommandReorder) + { + ScComplexRefData aRefData; + aRefData.InitFlags(); + ScRange aRange; + pDBData->GetArea(aRange); + aRange.aEnd.SetTab(aRange.aStart.Tab()); + aRefData.SetRange(rDoc.GetSheetLimits(), aRange, aPos); + ScTokenArray* pNew = new ScTokenArray(rDoc); + pNew->AddDoubleReference( aRefData ); + PushTokenArray( pNew, true ); + return GetToken(); + } + return true; +} + +bool ScCompiler::GetTokenIfOpCode( OpCode eOp ) +{ + const formula::FormulaToken* p = maArrIterator.PeekNextNoSpaces(); + if (p && p->GetOpCode() == eOp) + return GetToken(); + return false; +} + + +/* Documentation on MS-Excel Table structured references: + * https://support.office.com/en-us/article/Use-structured-references-in-Excel-table-formulas-75fb07d3-826a-449c-b76f-363057e3d16f + * * as of Excel 2013 + * [MS-XLSX]: Formulas https://msdn.microsoft.com/en-us/library/dd906358.aspx + * * look for structure-reference + */ + +bool ScCompiler::HandleTableRef() +{ + ScTableRefToken* pTR = dynamic_cast<ScTableRefToken*>(mpToken.get()); + if (!pTR) + { + SetError(FormulaError::UnknownToken); + return true; + } + + ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex( pTR->GetIndex()); + if ( !pDBData ) + SetError(FormulaError::NoName); + else if (mbJumpCommandReorder) + { + ScRange aDBRange; + pDBData->GetArea(aDBRange); + aDBRange.aEnd.SetTab(aDBRange.aStart.Tab()); + ScRange aRange( aDBRange); + FormulaError nError = FormulaError::NONE; + bool bForwardToClose = false; + ScTableRefToken::Item eItem = pTR->GetItem(); + switch (eItem) + { + case ScTableRefToken::TABLE: + { + // The table name without items references the table data, + // without headers or totals. + if (pDBData->HasHeader()) + aRange.aStart.IncRow(); + if (pDBData->HasTotals()) + aRange.aEnd.IncRow(-1); + if (aRange.aEnd.Row() < aRange.aStart.Row()) + nError = FormulaError::NoRef; + bForwardToClose = true; + } + break; + case ScTableRefToken::ALL: + { + bForwardToClose = true; + } + break; + case ScTableRefToken::HEADERS: + { + if (pDBData->HasHeader()) + aRange.aEnd.SetRow( aRange.aStart.Row()); + else + nError = FormulaError::NoRef; + bForwardToClose = true; + } + break; + case ScTableRefToken::DATA: + { + if (pDBData->HasHeader()) + aRange.aStart.IncRow(); + } + [[fallthrough]]; + case ScTableRefToken::HEADERS_DATA: + { + if (pDBData->HasTotals()) + aRange.aEnd.IncRow(-1); + if (aRange.aEnd.Row() < aRange.aStart.Row()) + nError = FormulaError::NoRef; + bForwardToClose = true; + } + break; + case ScTableRefToken::TOTALS: + { + if (pDBData->HasTotals()) + aRange.aStart.SetRow( aRange.aEnd.Row()); + else + nError = FormulaError::NoRef; + bForwardToClose = true; + } + break; + case ScTableRefToken::DATA_TOTALS: + { + if (pDBData->HasHeader()) + aRange.aStart.IncRow(); + if (aRange.aEnd.Row() < aRange.aStart.Row()) + nError = FormulaError::NoRef; + bForwardToClose = true; + } + break; + case ScTableRefToken::THIS_ROW: + { + if (aRange.aStart.Row() <= aPos.Row() && aPos.Row() <= aRange.aEnd.Row()) + { + aRange.aStart.SetRow( aPos.Row()); + aRange.aEnd.SetRow( aPos.Row()); + } + else + { + nError = FormulaError::NoValue; + // For *some* relative row reference in named + // expressions' thisrow special handling below. + aRange.aEnd.SetRow( aRange.aStart.Row()); + } + bForwardToClose = true; + } + break; + } + bool bColumnRange = false; + bool bCol1Rel = false; + bool bCol1RelName = false; + int nLevel = 0; + if (bForwardToClose && GetTokenIfOpCode( ocTableRefOpen)) + { + ++nLevel; + enum + { + sOpen, + sItem, + sClose, + sSep, + sLast, + sStop + } eState = sOpen; + do + { + const formula::FormulaToken* p = maArrIterator.PeekNextNoSpaces(); + if (!p) + eState = sStop; + else + { + switch (p->GetOpCode()) + { + case ocTableRefOpen: + eState = ((eState == sOpen || eState == sSep) ? sOpen : sStop); + if (++nLevel > 2) + { + SetError( FormulaError::Pair); + eState = sStop; + } + break; + case ocTableRefItemAll: + case ocTableRefItemHeaders: + case ocTableRefItemData: + case ocTableRefItemTotals: + case ocTableRefItemThisRow: + eState = ((eState == sOpen) ? sItem : sStop); + break; + case ocTableRefClose: + eState = ((eState == sItem || eState == sClose) ? sClose : sStop); + if (eState != sStop && --nLevel == 0) + eState = sLast; + break; + case ocSep: + eState = ((eState == sClose) ? sSep : sStop); + break; + case ocPush: + if (eState == sOpen && p->GetType() == svSingleRef) + { + bColumnRange = true; + bCol1Rel = p->GetSingleRef()->IsColRel(); + bCol1RelName = p->GetSingleRef()->IsRelName(); + eState = sLast; + } + else + { + eState = sStop; + } + break; + case ocBad: + eState = sLast; + if (nError == FormulaError::NONE) + nError = FormulaError::NoName; + break; + default: + eState = sStop; + } + if (eState != sStop) + GetToken(); + if (eState == sLast) + eState = sStop; + } + } while (eState != sStop); + } + ScTokenArray* pNew = new ScTokenArray(rDoc); + if (nError == FormulaError::NONE || nError == FormulaError::NoValue) + { + bool bCol2Rel = false; + bool bCol2RelName = false; + // The FormulaError::NoValue case generates a thisrow reference that can be + // used to save named expressions in A1 syntax notation. + if (bColumnRange) + { + // Limit range to specified columns. + ScRange aColRange( ScAddress::INITIALIZE_INVALID ); + switch (mpToken->GetType()) + { + case svSingleRef: + { + aColRange.aStart = aColRange.aEnd = mpToken->GetSingleRef()->toAbs(rDoc, aPos); + if ( GetTokenIfOpCode( ocTableRefClose) && (nLevel--) && + GetTokenIfOpCode( ocRange) && + GetTokenIfOpCode( ocTableRefOpen) && (++nLevel) && + GetTokenIfOpCode( ocPush)) + { + if (mpToken->GetType() != svSingleRef) + aColRange = ScRange( ScAddress::INITIALIZE_INVALID); + else + { + aColRange.aEnd = mpToken->GetSingleRef()->toAbs(rDoc, aPos); + aColRange.PutInOrder(); + bCol2Rel = mpToken->GetSingleRef()->IsColRel(); + bCol2RelName = mpToken->GetSingleRef()->IsRelName(); + } + } + } + break; + default: + ; // nothing + } + // coverity[copy_paste_error : FALSE] - this is correct, aStart in both aDBRange uses + if (aColRange.aStart.Row() != aDBRange.aStart.Row() || aColRange.aEnd.Row() != aDBRange.aStart.Row()) + aRange = ScRange( ScAddress::INITIALIZE_INVALID); + else + { + aColRange.aEnd.SetRow( aRange.aEnd.Row()); + aRange = aRange.Intersection( aColRange); + } + } + if (aRange.IsValid()) + { + if (aRange.aStart == aRange.aEnd) + { + ScSingleRefData aRefData; + aRefData.InitFlags(); + aRefData.SetColRel( bCol1Rel); + if (eItem == ScTableRefToken::THIS_ROW) + { + aRefData.SetRowRel( true); + if (!bCol1RelName) + bCol1RelName = pArr->IsFromRangeName(); + } + aRefData.SetRelName( bCol1RelName); + aRefData.SetFlag3D( true); + if (nError != FormulaError::NONE) + { + aRefData.SetAddress( rDoc.GetSheetLimits(), aRange.aStart, aRange.aStart); + pTR->SetAreaRefRPN( new ScSingleRefToken(rDoc.GetSheetLimits(), aRefData)); // set reference at TableRef + pNew->Add( new FormulaErrorToken( nError)); // set error in RPN + } + else + { + aRefData.SetAddress( rDoc.GetSheetLimits(), aRange.aStart, aPos); + pTR->SetAreaRefRPN( pNew->AddSingleReference( aRefData)); + } + } + else + { + ScComplexRefData aRefData; + aRefData.InitFlags(); + aRefData.Ref1.SetColRel( bCol1Rel); + aRefData.Ref2.SetColRel( bCol2Rel); + bool bRelName = bCol1RelName || bCol2RelName; + if (eItem == ScTableRefToken::THIS_ROW) + { + aRefData.Ref1.SetRowRel( true); + aRefData.Ref2.SetRowRel( true); + if (!bRelName) + bRelName = pArr->IsFromRangeName(); + } + aRefData.Ref1.SetRelName( bRelName); + aRefData.Ref2.SetRelName( bRelName); + aRefData.Ref1.SetFlag3D( true); + if (nError != FormulaError::NONE) + { + aRefData.SetRange( rDoc.GetSheetLimits(), aRange, aRange.aStart); + pTR->SetAreaRefRPN( new ScDoubleRefToken(rDoc.GetSheetLimits(), aRefData)); // set reference at TableRef + pNew->Add( new FormulaErrorToken( nError)); // set error in RPN + } + else + { + aRefData.SetRange( rDoc.GetSheetLimits(), aRange, aPos); + pTR->SetAreaRefRPN( pNew->AddDoubleReference( aRefData)); + } + } + } + else + { + pTR->SetAreaRefRPN( pNew->Add( new FormulaErrorToken( FormulaError::NoRef))); + } + } + else + { + pTR->SetAreaRefRPN( pNew->Add( new FormulaErrorToken( nError))); + } + while (nLevel-- > 0) + { + if (!GetTokenIfOpCode( ocTableRefClose)) + SetError( FormulaError::Pair); + } + PushTokenArray( pNew, true ); + return GetToken(); + } + return true; +} + +formula::ParamClass ScCompiler::GetForceArrayParameter( const formula::FormulaToken* pToken, sal_uInt16 nParam ) const +{ + return ScParameterClassification::GetParameterType( pToken, nParam); +} + +bool ScCompiler::ParameterMayBeImplicitIntersection(const FormulaToken* token, int parameter) +{ + formula::ParamClass param = ScParameterClassification::GetParameterType( token, parameter ); + return param == Value || param == Array; +} + +bool ScCompiler::SkipImplicitIntersectionOptimization(const FormulaToken* token) const +{ + if (mbMatrixFlag) + return true; + formula::ParamClass paramClass = token->GetInForceArray(); + if (paramClass == formula::ForceArray + || paramClass == formula::ReferenceOrForceArray + || paramClass == formula::SuppressedReferenceOrForceArray + || paramClass == formula::ReferenceOrRefArray) + { + return true; + } + formula::ParamClass returnType = ScParameterClassification::GetParameterType( token, SAL_MAX_UINT16 ); + return returnType == formula::Reference; +} + +void ScCompiler::HandleIIOpCode(FormulaToken* token, FormulaToken*** pppToken, sal_uInt8 nNumParams) +{ + if (!mbComputeII) + return; +#ifdef DBG_UTIL + if(!HandleIIOpCodeInternal(token, pppToken, nNumParams)) + mUnhandledPossibleImplicitIntersectionsOpCodes.insert(token->GetOpCode()); +#else + HandleIIOpCodeInternal(token, pppToken, nNumParams); +#endif +} + +// return true if opcode is handled +bool ScCompiler::HandleIIOpCodeInternal(FormulaToken* token, FormulaToken*** pppToken, sal_uInt8 nNumParams) +{ + if (nNumParams > 0 && *pppToken[0] == nullptr) + return false; // Bad expression (see the dummy creation in FormulaCompiler::CompileTokenArray()) + + const OpCode nOpCode = token->GetOpCode(); + + if (nOpCode == ocPush) + { + if(token->GetType() == svDoubleRef) + mUnhandledPossibleImplicitIntersections.insert( token ); + return true; + } + else if (nOpCode == ocSumIf || nOpCode == ocAverageIf) + { + if (nNumParams != 3) + return false; + + if (!(pppToken[0] && pppToken[2] && *pppToken[0] && *pppToken[2])) + return false; + + if ((*pppToken[0])->GetType() != svDoubleRef) + return false; + + const StackVar eSumRangeType = (*pppToken[2])->GetType(); + + if ( eSumRangeType != svSingleRef && eSumRangeType != svDoubleRef ) + return false; + + const ScComplexRefData& rBaseRange = *(*pppToken[0])->GetDoubleRef(); + + ScComplexRefData aSumRange; + if (eSumRangeType == svSingleRef) + { + aSumRange.Ref1 = *(*pppToken[2])->GetSingleRef(); + aSumRange.Ref2 = aSumRange.Ref1; + } + else + aSumRange = *(*pppToken[2])->GetDoubleRef(); + + CorrectSumRange(rBaseRange, aSumRange, pppToken[2]); + // TODO mark parameters as handled + return true; + } + else if (nOpCode >= SC_OPCODE_START_1_PAR && nOpCode < SC_OPCODE_STOP_1_PAR) + { + if (nNumParams != 1) + return false; + + if( !ParameterMayBeImplicitIntersection( token, 0 )) + return false; + if (SkipImplicitIntersectionOptimization(token)) + return false; + + if ((*pppToken[0])->GetType() != svDoubleRef) + return false; + + mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token ); + return true; + } + else if ((nOpCode >= SC_OPCODE_START_BIN_OP && nOpCode < SC_OPCODE_STOP_BIN_OP + && nOpCode != ocAnd && nOpCode != ocOr) + || nOpCode == ocRound || nOpCode == ocRoundUp || nOpCode == ocRoundDown) + { + if (nNumParams != 2) + return false; + + if( !ParameterMayBeImplicitIntersection( token, 0 ) || !ParameterMayBeImplicitIntersection( token, 1 )) + return false; + if (SkipImplicitIntersectionOptimization(token)) + return false; + + // Convert only if the other parameter is not a matrix (which would force the result to be a matrix). + if ((*pppToken[0])->GetType() == svDoubleRef && (*pppToken[1])->GetType() != svMatrix) + mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token ); + if ((*pppToken[1])->GetType() == svDoubleRef && (*pppToken[0])->GetType() != svMatrix) + mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[1], token ); + return true; + } + else if ((nOpCode >= SC_OPCODE_START_UN_OP && nOpCode < SC_OPCODE_STOP_UN_OP) + || nOpCode == ocPercentSign) + { + if (nNumParams != 1) + return false; + + if( !ParameterMayBeImplicitIntersection( token, 0 )) + return false; + if (SkipImplicitIntersectionOptimization(token)) + return false; + + if ((*pppToken[0])->GetType() == svDoubleRef) + mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token ); + return true; + } + else if (nOpCode == ocVLookup) + { + if (nNumParams != 3 && nNumParams != 4) + return false; + + if (SkipImplicitIntersectionOptimization(token)) + return false; + + assert( ParameterMayBeImplicitIntersection( token, 0 )); + assert( !ParameterMayBeImplicitIntersection( token, 1 )); + assert( ParameterMayBeImplicitIntersection( token, 2 )); + assert( ParameterMayBeImplicitIntersection( token, 3 )); + if ((*pppToken[2])->GetType() == svDoubleRef) + mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[2], token ); + if ((*pppToken[0])->GetType() == svDoubleRef) + mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token ); + if (nNumParams == 4 && (*pppToken[3])->GetType() == svDoubleRef) + mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[3], token ); + // a range for the second parameters is not an implicit intersection + mUnhandledPossibleImplicitIntersections.erase( *pppToken[ 1 ] ); + return true; + } + else + { + bool possibleII = false; + for( int i = 0; i < nNumParams; ++i ) + { + if( ParameterMayBeImplicitIntersection( token, i ) + && (*pppToken[i])->GetType() == svDoubleRef) + { + possibleII = true; + break; + } + } + if( !possibleII ) + { + // all parameters have been handled, they are not implicit intersections + for( int i = 0; i < nNumParams; ++i ) + mUnhandledPossibleImplicitIntersections.erase( *pppToken[ i ] ); + return true; + } + } + + return false; +} + +void ScCompiler::PostProcessCode() +{ + for( const PendingImplicitIntersectionOptimization& item : mPendingImplicitIntersectionOptimizations ) + { + if( *item.parameterLocation != item.parameter ) // the parameter has been changed somehow + continue; + if( item.parameterLocation >= pCode ) // the location is not inside the code (pCode points after the end) + continue; + // E.g. "SUMPRODUCT(I5:I6+1)" shouldn't do implicit intersection. + if( item.operation->IsInForceArray()) + continue; + ReplaceDoubleRefII( item.parameterLocation ); + } + mPendingImplicitIntersectionOptimizations.clear(); +} + +void ScCompiler::AnnotateOperands() +{ + AnnotateTrimOnDoubleRefs(); +} + +void ScCompiler::ReplaceDoubleRefII(FormulaToken** ppDoubleRefTok) +{ + const ScComplexRefData* pRange = (*ppDoubleRefTok)->GetDoubleRef(); + if (!pRange) + return; + + const ScComplexRefData& rRange = *pRange; + + // Can't do optimization reliably in this case (when row references are absolute). + // Example : =SIN(A$1:A$10) filled in a formula group starting at B5 and of length 100. + // If we just optimize the argument $A$1:$A$10 to singleref "A5" for the top cell in the fg, then + // the results in cells B11:B104 will be incorrect (sin(0) = 0, assuming empty cells in A11:A104) + // instead of the #VALUE! errors we would expect. We need to know the formula-group length to + // fix this, but that is unknown at this stage, so skip such cases. + if (!rRange.Ref1.IsRowRel() && !rRange.Ref2.IsRowRel()) + return; + + ScRange aAbsRange = rRange.toAbs(rDoc, aPos); + if (aAbsRange.aStart == aAbsRange.aEnd) + return; // Nothing to do (trivial case). + + ScAddress aAddr; + + if (!DoubleRefToPosSingleRefScalarCase(aAbsRange, aAddr, aPos)) + return; + + ScSingleRefData aSingleRef; + aSingleRef.InitFlags(); + aSingleRef.SetColRel(rRange.Ref1.IsColRel()); + aSingleRef.SetRowRel(true); + aSingleRef.SetTabRel(rRange.Ref1.IsTabRel()); + aSingleRef.SetAddress(rDoc.GetSheetLimits(), aAddr, aPos); + + // Replace the original doubleref token with computed singleref token + FormulaToken* pNewSingleRefTok = new ScSingleRefToken(rDoc.GetSheetLimits(), aSingleRef); + (*ppDoubleRefTok)->DecRef(); + *ppDoubleRefTok = pNewSingleRefTok; + pNewSingleRefTok->IncRef(); +} + +bool ScCompiler::DoubleRefToPosSingleRefScalarCase(const ScRange& rRange, ScAddress& rAdr, const ScAddress& rFormulaPos) +{ + assert(rRange.aStart != rRange.aEnd); + + bool bOk = false; + SCCOL nMyCol = rFormulaPos.Col(); + SCROW nMyRow = rFormulaPos.Row(); + SCTAB nMyTab = rFormulaPos.Tab(); + SCCOL nCol = 0; + SCROW nRow = 0; + SCTAB nTab; + nTab = rRange.aStart.Tab(); + if ( rRange.aStart.Col() <= nMyCol && nMyCol <= rRange.aEnd.Col() ) + { + nRow = rRange.aStart.Row(); + if ( nRow == rRange.aEnd.Row() ) + { + bOk = true; + nCol = nMyCol; + } + else if ( nTab != nMyTab && nTab == rRange.aEnd.Tab() + && rRange.aStart.Row() <= nMyRow && nMyRow <= rRange.aEnd.Row() ) + { + bOk = true; + nCol = nMyCol; + nRow = nMyRow; + } + } + else if ( rRange.aStart.Row() <= nMyRow && nMyRow <= rRange.aEnd.Row() ) + { + nCol = rRange.aStart.Col(); + if ( nCol == rRange.aEnd.Col() ) + { + bOk = true; + nRow = nMyRow; + } + else if ( nTab != nMyTab && nTab == rRange.aEnd.Tab() + && rRange.aStart.Col() <= nMyCol && nMyCol <= rRange.aEnd.Col() ) + { + bOk = true; + nCol = nMyCol; + nRow = nMyRow; + } + } + if ( bOk ) + { + if ( nTab == rRange.aEnd.Tab() ) + ; // all done + else if ( nTab <= nMyTab && nMyTab <= rRange.aEnd.Tab() ) + nTab = nMyTab; + else + bOk = false; + if ( bOk ) + rAdr.Set( nCol, nRow, nTab ); + } + + return bOk; +} + +static void lcl_GetColRowDeltas(const ScRange& rRange, SCCOL& rXDelta, SCROW& rYDelta) +{ + rXDelta = rRange.aEnd.Col() - rRange.aStart.Col(); + rYDelta = rRange.aEnd.Row() - rRange.aStart.Row(); +} + +bool ScCompiler::AdjustSumRangeShape(const ScComplexRefData& rBaseRange, ScComplexRefData& rSumRange) +{ + ScRange aAbs = rSumRange.toAbs(rDoc, aPos); + + // Current sum-range end col/row + SCCOL nEndCol = aAbs.aEnd.Col(); + SCROW nEndRow = aAbs.aEnd.Row(); + + // Current behaviour is, we will get a #NAME? for the below case, so bail out. + // Note that sum-range's End[Col,Row] are same as Start[Col,Row] if the original formula + // has a single-ref as the sum-range. + if (!rDoc.ValidCol(nEndCol) || !rDoc.ValidRow(nEndRow)) + return false; + + SCCOL nXDeltaSum = 0; + SCROW nYDeltaSum = 0; + + lcl_GetColRowDeltas(aAbs, nXDeltaSum, nYDeltaSum); + + aAbs = rBaseRange.toAbs(rDoc, aPos); + SCCOL nXDelta = 0; + SCROW nYDelta = 0; + + lcl_GetColRowDeltas(aAbs, nXDelta, nYDelta); + + if (nXDelta == nXDeltaSum && + nYDelta == nYDeltaSum) + return false; // shapes of base-range match current sum-range + + // Try to make the sum-range to take the same shape as base-range, + // by adjusting Ref2 member of rSumRange if the resultant sum-range don't + // go out-of-bounds. + + SCCOL nXInc = nXDelta - nXDeltaSum; + SCROW nYInc = nYDelta - nYDeltaSum; + + // Don't let a valid End[Col,Row] go beyond (rDoc.MaxCol(),rDoc.MaxRow()) to match + // what happens in ScInterpreter::IterateParametersIf(), but there it also shrinks + // the base-range by the (out-of-bound)amount clipped off the sum-range. + // TODO: Probably we can optimize (from threading perspective) rBaseRange + // by shrinking it here correspondingly (?) + if (nEndCol + nXInc > rDoc.MaxCol()) + nXInc = rDoc.MaxCol() - nEndCol; + if (nEndRow + nYInc > rDoc.MaxRow()) + nYInc = rDoc.MaxRow() - nEndRow; + + rSumRange.Ref2.IncCol(nXInc); + rSumRange.Ref2.IncRow(nYInc); + + return true; +} + +void ScCompiler::CorrectSumRange(const ScComplexRefData& rBaseRange, + ScComplexRefData& rSumRange, + FormulaToken** ppSumRangeToken) +{ + if (!AdjustSumRangeShape(rBaseRange, rSumRange)) + return; + + // Replace sum-range token + FormulaToken* pNewSumRangeTok = new ScDoubleRefToken(rDoc.GetSheetLimits(), rSumRange); + (*ppSumRangeToken)->DecRef(); + *ppSumRangeToken = pNewSumRangeTok; + pNewSumRangeTok->IncRef(); +} + +void ScCompiler::AnnotateTrimOnDoubleRefs() +{ + if (!pCode || !(*(pCode - 1))) + return; + + // OpCode of the "root" operator (which is already in RPN array). + OpCode eOpCode = (*(pCode - 1))->GetOpCode(); + // eOpCode can be some operator which does not change with operands with or contains zero values. + if (eOpCode == ocSum) + { + FormulaToken** ppTok = pCode - 2; // exclude the root operator. + // The following loop runs till a "pattern" is found or there is a mismatch + // and marks the push DoubleRef arguments as trimmable when there is a match. + // The pattern is + // SUM(IF(<reference|double>=<reference|double>, <then-clause>)<a some operands with operators / or *>) + // such that one of the operands of ocEqual is a double-ref. + // Examples of formula that matches this are: + // SUM(IF(D:D=$A$1,F:F)*$H$1*2.3/$G$2) + // SUM((IF(D:D=$A$1,F:F)*$H$1*2.3/$G$2)*$H$2*5/$G$3) + // SUM(IF(E:E=16,F:F)*$H$1*100) + bool bTillClose = true; + bool bCloseTillIf = false; + sal_Int16 nToksTillIf = 0; + constexpr sal_Int16 MAXDIST_IF = 15; + while (*ppTok) + { + FormulaToken* pTok = *ppTok; + OpCode eCurrOp = pTok->GetOpCode(); + ++nToksTillIf; + + // TODO : Is there a better way to handle this ? + // ocIf is too far off from the sum opcode. + if (nToksTillIf > MAXDIST_IF) + return; + + switch (eCurrOp) + { + case ocDiv: + case ocMul: + if (!bTillClose) + return; + break; + case ocPush: + + break; + case ocClose: + if (bTillClose) + { + bTillClose = false; + bCloseTillIf = true; + } + else + return; + break; + case ocIf: + { + if (!bCloseTillIf) + return; + + if (!pTok->IsInForceArray()) + return; + + const short nJumpCount = pTok->GetJump()[0]; + if (nJumpCount != 2) // Should have THEN but no ELSE. + return; + + OpCode eCompOp = (*(ppTok - 1))->GetOpCode(); + if (eCompOp != ocEqual) + return; + + FormulaToken* pLHS = *(ppTok - 2); + FormulaToken* pRHS = *(ppTok - 3); + if (((pLHS->GetType() == svSingleRef || pLHS->GetType() == svDouble) && pRHS->GetType() == svDoubleRef) || + ((pRHS->GetType() == svSingleRef || pRHS->GetType() == svDouble) && pLHS->GetType() == svDoubleRef)) + { + if (pLHS->GetType() == svDoubleRef) + pLHS->GetDoubleRef()->SetTrimToData(true); + else + pRHS->GetDoubleRef()->SetTrimToData(true); + return; + } + } + break; + default: + return; + } + --ppTok; + } + } + else if (eOpCode == ocSumProduct) + { + FormulaToken** ppTok = pCode - 2; // exclude the root operator. + // The following loop runs till a "pattern" is found or there is a mismatch + // and marks the push DoubleRef arguments as trimmable when there is a match. + // The pattern is + // SUMPRODUCT(IF(<reference|double>=<reference|double>, <then-clause>)<a some operands with operators / or *>) + // such that one of the operands of ocEqual is a double-ref. + // Examples of formula that matches this are: + // SUMPRODUCT(IF($A:$A=$L12;$D:$D*G:G)) + bool bTillClose = true; + bool bCloseTillIf = false; + sal_Int16 nToksTillIf = 0; + constexpr sal_Int16 MAXDIST_IF = 15; + while (*ppTok) + { + FormulaToken* pTok = *ppTok; + OpCode eCurrOp = pTok->GetOpCode(); + ++nToksTillIf; + + // TODO : Is there a better way to handle this ? + // ocIf is too far off from the sum opcode. + if (nToksTillIf > MAXDIST_IF) + return; + + switch (eCurrOp) + { + case ocDiv: + case ocMul: + { + if (!pTok->IsInForceArray()) + break; + FormulaToken* pLHS = *(ppTok - 1); + FormulaToken* pRHS = *(ppTok - 2); + StackVar lhsType = pLHS->GetType(); + StackVar rhsType = pRHS->GetType(); + if (lhsType == svDoubleRef && rhsType == svDoubleRef) + { + pLHS->GetDoubleRef()->SetTrimToData(true); + pRHS->GetDoubleRef()->SetTrimToData(true); + } + } + break; + case ocPush: + break; + case ocClose: + if (bTillClose) + { + bTillClose = false; + bCloseTillIf = true; + } + else + return; + break; + case ocIf: + { + if (!bCloseTillIf) + return; + + if (!pTok->IsInForceArray()) + return; + + const short nJumpCount = pTok->GetJump()[0]; + if (nJumpCount != 2) // Should have THEN but no ELSE. + return; + + OpCode eCompOp = (*(ppTok - 1))->GetOpCode(); + if (eCompOp != ocEqual) + return; + + FormulaToken* pLHS = *(ppTok - 2); + FormulaToken* pRHS = *(ppTok - 3); + StackVar lhsType = pLHS->GetType(); + StackVar rhsType = pRHS->GetType(); + if (lhsType == svDoubleRef && (rhsType == svSingleRef || rhsType == svDouble)) + { + pLHS->GetDoubleRef()->SetTrimToData(true); + } + if ((lhsType == svSingleRef || lhsType == svDouble) && rhsType == svDoubleRef) + { + pRHS->GetDoubleRef()->SetTrimToData(true); + } + return; + } + break; + default: + return; + } + --ppTok; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/consoli.cxx b/sc/source/core/tool/consoli.cxx new file mode 100644 index 0000000000..aa8dbcc3ee --- /dev/null +++ b/sc/source/core/tool/consoli.cxx @@ -0,0 +1,544 @@ +/* -*- 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 <consoli.hxx> +#include <document.hxx> +#include <olinetab.hxx> +#include <subtotal.hxx> +#include <formula/errorcodes.hxx> +#include <formulacell.hxx> +#include <tokenarray.hxx> +#include <osl/diagnose.h> +#include <refdata.hxx> + +#include <string.h> +#include <memory> + +#define SC_CONS_NOTFOUND -1 + +const OpCode eOpCodeTable[] = { // order as for enum ScSubTotalFunc + ocBad, // none + ocAverage, + ocCount, + ocCount2, + ocMax, + ocMin, + ocProduct, + ocStDev, + ocStDevP, + ocSum, + ocVar, + ocVarP }; + +template< typename T > +static void lcl_AddString( ::std::vector<OUString>& rData, T& nCount, const OUString& rInsert ) +{ + rData.push_back( rInsert); + ++nCount; +} + +ScConsData::ScConsData() : + eFunction(SUBTOTAL_FUNC_SUM), + bReference(false), + bColByName(false), + bRowByName(false), + nColCount(0), + nRowCount(0), + nDataCount(0), + bCornerUsed(false) +{ +} + +ScConsData::~ScConsData() +{ +} + +void ScConsData::DeleteData() +{ + ppRefs.reset(); + ppFunctionData.reset(); + ppUsed.reset(); + ppTitlePos.reset(); + ::std::vector<OUString>().swap( maColHeaders); + ::std::vector<OUString>().swap( maRowHeaders); + ::std::vector<OUString>().swap( maTitles); + nDataCount = 0; + + if (bColByName) nColCount = 0; // otherwise maColHeaders is wrong + if (bRowByName) nRowCount = 0; + + bCornerUsed = false; + aCornerText.clear(); +} + +void ScConsData::InitData() +{ + if (bReference && nColCount && !ppRefs) + { + ppRefs.reset(new std::unique_ptr<ScReferenceList[]>[nColCount]); + for (SCSIZE i=0; i<nColCount; i++) + ppRefs[i].reset(new ScReferenceList[nRowCount]); + } + else if (nColCount && !ppFunctionData) + { + ppFunctionData.reset( new std::unique_ptr<ScFunctionData[]>[nColCount] ); + for (SCSIZE i=0; i<nColCount; i++) + { + ppFunctionData[i].reset( new ScFunctionData[nRowCount] ); + } + } + + if (nColCount && !ppUsed) + { + ppUsed.reset( new std::unique_ptr<bool[]>[nColCount] ); + for (SCSIZE i=0; i<nColCount; i++) + { + ppUsed[i].reset( new bool[nRowCount] ); + memset( ppUsed[i].get(), 0, nRowCount * sizeof(bool) ); + } + } + + if (nRowCount && nDataCount && !ppTitlePos) + { + ppTitlePos.reset( new std::unique_ptr<SCSIZE[]>[nRowCount] ); + for (SCSIZE i=0; i<nRowCount; i++) + { + ppTitlePos[i].reset( new SCSIZE[nDataCount] ); + memset( ppTitlePos[i].get(), 0, nDataCount * sizeof(SCSIZE) ); //TODO: not necessary ? + } + } + + // CornerText: single String +} + +void ScConsData::DoneFields() +{ + InitData(); +} + +void ScConsData::SetSize( SCCOL nCols, SCROW nRows ) +{ + DeleteData(); + nColCount = static_cast<SCSIZE>(nCols); + nRowCount = static_cast<SCSIZE>(nRows); +} + +void ScConsData::GetSize( SCCOL& rCols, SCROW& rRows ) const +{ + rCols = static_cast<SCCOL>(nColCount); + rRows = static_cast<SCROW>(nRowCount); +} + +void ScConsData::SetFlags( ScSubTotalFunc eFunc, bool bColName, bool bRowName, bool bRef ) +{ + DeleteData(); + bReference = bRef; + bColByName = bColName; + if (bColName) nColCount = 0; + bRowByName = bRowName; + if (bRowName) nRowCount = 0; + eFunction = eFunc; +} + +void ScConsData::AddFields( const ScDocument* pSrcDoc, SCTAB nTab, + SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) +{ + ++nDataCount; + + OUString aTitle; + + SCCOL nStartCol = nCol1; + SCROW nStartRow = nRow1; + if (bColByName) ++nStartRow; + if (bRowByName) ++nStartCol; + + if (bColByName) + { + for (SCCOL nCol=nStartCol; nCol<=nCol2; nCol++) + { + aTitle = pSrcDoc->GetString(nCol, nRow1, nTab); + if (!aTitle.isEmpty()) + { + bool bFound = false; + for (SCSIZE i=0; i<nColCount && !bFound; i++) + if ( maColHeaders[i] == aTitle ) + bFound = true; + if (!bFound) + lcl_AddString( maColHeaders, nColCount, aTitle ); + } + } + } + + if (!bRowByName) + return; + + for (SCROW nRow=nStartRow; nRow<=nRow2; nRow++) + { + aTitle = pSrcDoc->GetString(nCol1, nRow, nTab); + if (!aTitle.isEmpty()) + { + bool bFound = false; + for (SCSIZE i=0; i<nRowCount && !bFound; i++) + if ( maRowHeaders[i] == aTitle ) + bFound = true; + if (!bFound) + lcl_AddString( maRowHeaders, nRowCount, aTitle ); + } + } +} + +void ScConsData::AddName( const OUString& rName ) +{ + SCSIZE nArrX; + SCSIZE nArrY; + + if (!bReference) + return; + + maTitles.push_back( rName); + size_t nTitleCount = maTitles.size(); + + for (nArrY=0; nArrY<nRowCount; nArrY++) + { + // set all data to same length + + SCSIZE nMax = 0; + for (nArrX=0; nArrX<nColCount; nArrX++) + nMax = std::max( nMax, ppRefs[nArrX][nArrY].size() ); + + for (nArrX=0; nArrX<nColCount; nArrX++) + { + ppUsed[nArrX][nArrY] = true; + ppRefs[nArrX][nArrY].resize( nMax, { SC_CONS_NOTFOUND, SC_CONS_NOTFOUND, SC_CONS_NOTFOUND }); + } + + // store positions + + if (ppTitlePos) + if (nTitleCount < nDataCount) + ppTitlePos[nArrY][nTitleCount] = nMax; + } +} + +void ScConsData::AddData( ScDocument* pSrcDoc, SCTAB nTab, + SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) +{ + PutInOrder(nCol1,nCol2); + PutInOrder(nRow1,nRow2); + if ( nCol2 >= sal::static_int_cast<SCCOL>(nCol1 + nColCount) && !bColByName ) + { + OSL_FAIL("range too big"); + nCol2 = sal::static_int_cast<SCCOL>( nCol1 + nColCount - 1 ); + } + if ( nRow2 >= sal::static_int_cast<SCROW>(nRow1 + nRowCount) && !bRowByName ) + { + OSL_FAIL("range too big"); + nRow2 = sal::static_int_cast<SCROW>( nRow1 + nRowCount - 1 ); + } + + SCCOL nCol; + SCROW nRow; + + // left top corner + + if ( bColByName && bRowByName ) + { + OUString aThisCorner = pSrcDoc->GetString(nCol1, nRow1, nTab); + if (bCornerUsed) + { + if (aCornerText != aThisCorner) + aCornerText.clear(); + } + else + { + aCornerText = aThisCorner; + bCornerUsed = true; + } + } + + // search title + + SCCOL nStartCol = nCol1; + SCROW nStartRow = nRow1; + if (bColByName) ++nStartRow; + if (bRowByName) ++nStartCol; + OUString aTitle; + std::unique_ptr<SCCOL[]> pDestCols; + std::unique_ptr<SCROW[]> pDestRows; + if (bColByName) + { + pDestCols.reset(new SCCOL[nCol2-nStartCol+1]); + for (nCol=nStartCol; nCol<=nCol2; nCol++) + { + aTitle = pSrcDoc->GetString(nCol, nRow1, nTab); + SCCOL nPos = SC_CONS_NOTFOUND; + if (!aTitle.isEmpty()) + { + bool bFound = false; + for (SCSIZE i=0; i<nColCount && !bFound; i++) + if ( maColHeaders[i] == aTitle ) + { + nPos = static_cast<SCCOL>(i); + bFound = true; + } + OSL_ENSURE(bFound, "column not found"); + } + pDestCols[nCol-nStartCol] = nPos; + } + } + if (bRowByName) + { + pDestRows.reset(new SCROW[nRow2-nStartRow+1]); + for (nRow=nStartRow; nRow<=nRow2; nRow++) + { + aTitle = pSrcDoc->GetString(nCol1, nRow, nTab); + SCROW nPos = SC_CONS_NOTFOUND; + if (!aTitle.isEmpty()) + { + bool bFound = false; + for (SCSIZE i=0; i<nRowCount && !bFound; i++) + if ( maRowHeaders[i] == aTitle ) + { + nPos = static_cast<SCROW>(i); + bFound = true; + } + OSL_ENSURE(bFound, "row not found"); + } + pDestRows[nRow-nStartRow] = nPos; + } + } + nCol1 = nStartCol; + nRow1 = nStartRow; + + // data + + bool bAnyCell = ( eFunction == SUBTOTAL_FUNC_CNT2 ); + for (nCol=nCol1; nCol<=nCol2; nCol++) + { + SCCOL nArrX = nCol-nCol1; + if (bColByName) nArrX = pDestCols[nArrX]; + if (nArrX != SC_CONS_NOTFOUND) + { + for (nRow=nRow1; nRow<=nRow2; nRow++) + { + SCROW nArrY = nRow-nRow1; + if (bRowByName) nArrY = pDestRows[nArrY]; + if ( nArrY != SC_CONS_NOTFOUND && ( + bAnyCell ? pSrcDoc->HasData( nCol, nRow, nTab ) + : pSrcDoc->HasValueData( nCol, nRow, nTab ) ) ) + { + if (bReference) + { + ppUsed[nArrX][nArrY] = true; + ppRefs[nArrX][nArrY].push_back( { nCol, nRow, nTab } ); + } + else + { + double nVal = pSrcDoc->GetValue( nCol, nRow, nTab ); + if (!ppUsed[nArrX][nArrY]) + { + ppUsed[nArrX][nArrY] = true; + ppFunctionData[nArrX][nArrY] = ScFunctionData( eFunction); + } + ppFunctionData[nArrX][nArrY].update( nVal); + } + } + } + } + } +} + +// check before, how many rows to insert (for Undo) + +SCROW ScConsData::GetInsertCount() const +{ + SCROW nInsert = 0; + SCSIZE nArrX; + SCSIZE nArrY; + if ( ppRefs && ppUsed ) + { + for (nArrY=0; nArrY<nRowCount; nArrY++) + { + SCSIZE nNeeded = 0; + for (nArrX=0; nArrX<nColCount; nArrX++) + nNeeded = std::max( nNeeded, ppRefs[nArrX][nArrY].size() ); + + nInsert += nNeeded; + } + } + return nInsert; +} + +// store completed data to document +//TODO: optimize on columns? + +void ScConsData::OutputToDocument( ScDocument& rDestDoc, SCCOL nCol, SCROW nRow, SCTAB nTab ) +{ + OpCode eOpCode = eOpCodeTable[eFunction]; + + SCSIZE nArrX; + SCSIZE nArrY; + + // left top corner + + if ( bColByName && bRowByName && !aCornerText.isEmpty() ) + rDestDoc.SetString( nCol, nRow, nTab, aCornerText ); + + // title + + SCCOL nStartCol = nCol; + SCROW nStartRow = nRow; + if (bColByName) ++nStartRow; + if (bRowByName) ++nStartCol; + + if (bColByName) + for (SCSIZE i=0; i<nColCount; i++) + rDestDoc.SetString( sal::static_int_cast<SCCOL>(nStartCol+i), nRow, nTab, maColHeaders[i] ); + if (bRowByName) + for (SCSIZE j=0; j<nRowCount; j++) + rDestDoc.SetString( nCol, sal::static_int_cast<SCROW>(nStartRow+j), nTab, maRowHeaders[j] ); + + nCol = nStartCol; + nRow = nStartRow; + + // data + + if ( ppFunctionData && ppUsed ) // insert values directly + { + for (nArrX=0; nArrX<nColCount; nArrX++) + for (nArrY=0; nArrY<nRowCount; nArrY++) + if (ppUsed[nArrX][nArrY]) + { + double fVal = ppFunctionData[nArrX][nArrY].getResult(); + if (ppFunctionData[nArrX][nArrY].getError()) + rDestDoc.SetError( sal::static_int_cast<SCCOL>(nCol+nArrX), + sal::static_int_cast<SCROW>(nRow+nArrY), nTab, FormulaError::NoValue ); + else + rDestDoc.SetValue( sal::static_int_cast<SCCOL>(nCol+nArrX), + sal::static_int_cast<SCROW>(nRow+nArrY), nTab, fVal ); + } + } + + if ( !(ppRefs && ppUsed) ) // insert Reference + return; + + //TODO: differentiate, if split into categories + OUString aString; + + ScSingleRefData aSRef; // data for Reference formula cells + aSRef.InitFlags(); // this reference is absolute at all times + aSRef.SetFlag3D(true); + + ScComplexRefData aCRef; // data for Sum cells + aCRef.InitFlags(); + aCRef.Ref1.SetColRel(true); aCRef.Ref1.SetRowRel(true); aCRef.Ref1.SetTabRel(true); + aCRef.Ref2.SetColRel(true); aCRef.Ref2.SetRowRel(true); aCRef.Ref2.SetTabRel(true); + + for (nArrY=0; nArrY<nRowCount; nArrY++) + { + SCSIZE nNeeded = 0; + for (nArrX=0; nArrX<nColCount; nArrX++) + nNeeded = std::max( nNeeded, ppRefs[nArrX][nArrY].size() ); + + if (nNeeded) + { + rDestDoc.InsertRow( 0,nTab, rDestDoc.MaxCol(),nTab, nRow+nArrY, nNeeded ); + + for (nArrX=0; nArrX<nColCount; nArrX++) + if (ppUsed[nArrX][nArrY]) + { + SCSIZE nCount = ppRefs[nArrX][nArrY].size(); + if (nCount) + { + for (SCSIZE nPos=0; nPos<nCount; nPos++) + { + ScReferenceEntry aRef = ppRefs[nArrX][nArrY][nPos]; + if (aRef.nTab != SC_CONS_NOTFOUND) + { + // insert reference (absolute, 3d) + + aSRef.SetAddress(rDestDoc.GetSheetLimits(), ScAddress(aRef.nCol,aRef.nRow,aRef.nTab), ScAddress()); + + ScTokenArray aRefArr(rDestDoc); + aRefArr.AddSingleReference(aSRef); + aRefArr.AddOpCode(ocStop); + ScAddress aDest( sal::static_int_cast<SCCOL>(nCol+nArrX), + sal::static_int_cast<SCROW>(nRow+nArrY+nPos), nTab ); + ScFormulaCell* pCell = new ScFormulaCell(rDestDoc, aDest, aRefArr); + rDestDoc.SetFormulaCell(aDest, pCell); + } + } + + // insert sum (relative, not 3d) + + ScAddress aDest( sal::static_int_cast<SCCOL>(nCol+nArrX), + sal::static_int_cast<SCROW>(nRow+nArrY+nNeeded), nTab ); + + ScRange aRange(sal::static_int_cast<SCCOL>(nCol+nArrX), nRow+nArrY, nTab); + aRange.aEnd.SetRow(nRow+nArrY+nNeeded-1); + aCRef.SetRange(rDestDoc.GetSheetLimits(), aRange, aDest); + + ScTokenArray aArr(rDestDoc); + aArr.AddOpCode(eOpCode); // selected function + aArr.AddOpCode(ocOpen); + aArr.AddDoubleReference(aCRef); + aArr.AddOpCode(ocClose); + aArr.AddOpCode(ocStop); + ScFormulaCell* pCell = new ScFormulaCell(rDestDoc, aDest, aArr); + rDestDoc.SetFormulaCell(aDest, pCell); + } + } + + // insert outline + + ScOutlineArray& rOutArr = rDestDoc.GetOutlineTable( nTab, true )->GetRowArray(); + SCROW nOutStart = nRow+nArrY; + SCROW nOutEnd = nRow+nArrY+nNeeded-1; + bool bSize = false; + rOutArr.Insert( nOutStart, nOutEnd, bSize ); + for (SCROW nOutRow=nOutStart; nOutRow<=nOutEnd; nOutRow++) + rDestDoc.ShowRow( nOutRow, nTab, false ); + rDestDoc.SetDrawPageSize(nTab); + rDestDoc.UpdateOutlineRow( nOutStart, nOutEnd, nTab, false ); + + // sub title + + if (ppTitlePos && !maTitles.empty() && !maRowHeaders.empty()) + { + for (SCSIZE nPos=0; nPos<nDataCount; nPos++) + { + SCSIZE nTPos = ppTitlePos[nArrY][nPos]; + bool bDo = true; + if (nPos+1<nDataCount) + if (ppTitlePos[nArrY][nPos+1] == nTPos) + bDo = false; // empty + if ( bDo && nTPos < nNeeded ) + { + aString = maRowHeaders[nArrY] + " / " + maTitles[nPos]; + rDestDoc.SetString( nCol-1, nRow+nArrY+nTPos, nTab, aString ); + } + } + } + + nRow += nNeeded; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/dbdata.cxx b/sc/source/core/tool/dbdata.cxx new file mode 100644 index 0000000000..c2096b39c4 --- /dev/null +++ b/sc/source/core/tool/dbdata.cxx @@ -0,0 +1,1656 @@ +/* -*- 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 <sal/config.h> +#include <sal/log.hxx> + +#include <o3tl/safeint.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <unotools/charclass.hxx> + +#include <dbdata.hxx> +#include <globalnames.hxx> +#include <refupdat.hxx> +#include <document.hxx> +#include <queryparam.hxx> +#include <queryentry.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <subtotalparam.hxx> +#include <sortparam.hxx> +#include <dociter.hxx> +#include <brdcst.hxx> + +#include <comphelper/stl_types.hxx> + +#include <memory> +#include <utility> + +bool ScDBData::less::operator() (const std::unique_ptr<ScDBData>& left, const std::unique_ptr<ScDBData>& right) const +{ + return ScGlobal::GetTransliteration().compareString(left->GetUpperName(), right->GetUpperName()) < 0; +} + +ScDBData::ScDBData( const OUString& rName, + SCTAB nTab, + SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + bool bByR, bool bHasH, bool bTotals) : + // Listeners are to be setup by the "parent" container. + mpSortParam(new ScSortParam), + mpQueryParam(new ScQueryParam), + mpSubTotal(new ScSubTotalParam), + mpImportParam(new ScImportParam), + mpContainer (nullptr), + aName (rName), + aUpper (rName), + nTable (nTab), + nStartCol (nCol1), + nStartRow (nRow1), + nEndCol (nCol2), + nEndRow (nRow2), + bByRow (bByR), + bHasHeader (bHasH), + bHasTotals (bTotals), + bDoSize (false), + bKeepFmt (false), + bStripData (false), + bIsAdvanced (false), + bDBSelection(false), + nIndex (0), + bAutoFilter (false), + bModified (false), + mbTableColumnNamesDirty(true), + nFilteredRowCount(SCSIZE_MAX) +{ + aUpper = ScGlobal::getCharClass().uppercase(aUpper); +} + +ScDBData::ScDBData( const ScDBData& rData ) : + // Listeners are to be setup by the "parent" container. + SvtListener (), + ScRefreshTimer ( rData ), + mpSortParam(new ScSortParam(*rData.mpSortParam)), + mpQueryParam(new ScQueryParam(*rData.mpQueryParam)), + mpSubTotal(new ScSubTotalParam(*rData.mpSubTotal)), + mpImportParam(new ScImportParam(*rData.mpImportParam)), + mpContainer (nullptr), + aName (rData.aName), + aUpper (rData.aUpper), + nTable (rData.nTable), + nStartCol (rData.nStartCol), + nStartRow (rData.nStartRow), + nEndCol (rData.nEndCol), + nEndRow (rData.nEndRow), + bByRow (rData.bByRow), + bHasHeader (rData.bHasHeader), + bHasTotals (rData.bHasTotals), + bDoSize (rData.bDoSize), + bKeepFmt (rData.bKeepFmt), + bStripData (rData.bStripData), + bIsAdvanced (rData.bIsAdvanced), + aAdvSource (rData.aAdvSource), + bDBSelection (rData.bDBSelection), + nIndex (rData.nIndex), + bAutoFilter (rData.bAutoFilter), + bModified (rData.bModified), + maTableColumnNames (rData.maTableColumnNames), + maTableColumnAttributes(rData.maTableColumnAttributes), + mbTableColumnNamesDirty(rData.mbTableColumnNamesDirty), + nFilteredRowCount (rData.nFilteredRowCount) +{ +} + +ScDBData::ScDBData( const OUString& rName, const ScDBData& rData ) : + // Listeners are to be setup by the "parent" container. + SvtListener (), + ScRefreshTimer ( rData ), + mpSortParam(new ScSortParam(*rData.mpSortParam)), + mpQueryParam(new ScQueryParam(*rData.mpQueryParam)), + mpSubTotal(new ScSubTotalParam(*rData.mpSubTotal)), + mpImportParam(new ScImportParam(*rData.mpImportParam)), + mpContainer (nullptr), + aName (rName), + aUpper (rName), + nTable (rData.nTable), + nStartCol (rData.nStartCol), + nStartRow (rData.nStartRow), + nEndCol (rData.nEndCol), + nEndRow (rData.nEndRow), + bByRow (rData.bByRow), + bHasHeader (rData.bHasHeader), + bHasTotals (rData.bHasTotals), + bDoSize (rData.bDoSize), + bKeepFmt (rData.bKeepFmt), + bStripData (rData.bStripData), + bIsAdvanced (rData.bIsAdvanced), + aAdvSource (rData.aAdvSource), + bDBSelection (rData.bDBSelection), + nIndex (rData.nIndex), + bAutoFilter (rData.bAutoFilter), + bModified (rData.bModified), + maTableColumnNames (rData.maTableColumnNames), + maTableColumnAttributes(rData.maTableColumnAttributes), + mbTableColumnNamesDirty (rData.mbTableColumnNamesDirty), + nFilteredRowCount (rData.nFilteredRowCount) +{ + aUpper = ScGlobal::getCharClass().uppercase(aUpper); +} + +ScDBData& ScDBData::operator= (const ScDBData& rData) +{ + if (this != &rData) + { + // Don't modify the name. The name is not mutable as it is used as a key + // in the container to keep the db ranges sorted by the name. + + bool bHeaderRangeDiffers = (nTable != rData.nTable || nStartCol != rData.nStartCol || + nEndCol != rData.nEndCol || nStartRow != rData.nStartRow); + bool bNeedsListening = ((bHasHeader && bHeaderRangeDiffers) || (!bHasHeader && rData.bHasHeader)); + if (bHasHeader && (!rData.bHasHeader || bHeaderRangeDiffers)) + { + EndTableColumnNamesListener(); + } + ScRefreshTimer::operator=( rData ); + mpSortParam.reset(new ScSortParam(*rData.mpSortParam)); + mpQueryParam.reset(new ScQueryParam(*rData.mpQueryParam)); + mpSubTotal.reset(new ScSubTotalParam(*rData.mpSubTotal)); + mpImportParam.reset(new ScImportParam(*rData.mpImportParam)); + // Keep mpContainer. + nTable = rData.nTable; + nStartCol = rData.nStartCol; + nStartRow = rData.nStartRow; + nEndCol = rData.nEndCol; + nEndRow = rData.nEndRow; + bByRow = rData.bByRow; + bHasHeader = rData.bHasHeader; + bHasTotals = rData.bHasTotals; + bDoSize = rData.bDoSize; + bKeepFmt = rData.bKeepFmt; + bStripData = rData.bStripData; + bIsAdvanced = rData.bIsAdvanced; + aAdvSource = rData.aAdvSource; + bDBSelection = rData.bDBSelection; + nIndex = rData.nIndex; + bAutoFilter = rData.bAutoFilter; + nFilteredRowCount = rData.nFilteredRowCount; + + if (bHeaderRangeDiffers) + InvalidateTableColumnNames( true); + else + { + maTableColumnNames = rData.maTableColumnNames; + maTableColumnAttributes = rData.maTableColumnAttributes; + mbTableColumnNamesDirty = rData.mbTableColumnNamesDirty; + } + + if (bNeedsListening) + StartTableColumnNamesListener(); + } + return *this; +} + +bool ScDBData::operator== (const ScDBData& rData) const +{ + // Data that is not in sort or query params. + + if ( nTable != rData.nTable || + bDoSize != rData.bDoSize || + bKeepFmt != rData.bKeepFmt || + bIsAdvanced!= rData.bIsAdvanced|| + bStripData != rData.bStripData || +// SAB: I think this should be here, but I don't want to break something +// bAutoFilter!= rData.bAutoFilter|| + ScRefreshTimer::operator!=( rData ) + ) + return false; + + if ( bIsAdvanced && aAdvSource != rData.aAdvSource ) + return false; + + ScSortParam aSort1, aSort2; + GetSortParam(aSort1); + rData.GetSortParam(aSort2); + if (!(aSort1 == aSort2)) + return false; + + ScQueryParam aQuery1, aQuery2; + GetQueryParam(aQuery1); + rData.GetQueryParam(aQuery2); + if (!(aQuery1 == aQuery2)) + return false; + + ScSubTotalParam aSubTotal1, aSubTotal2; + GetSubTotalParam(aSubTotal1); + rData.GetSubTotalParam(aSubTotal2); + if (!(aSubTotal1 == aSubTotal2)) + return false; + + ScImportParam aImport1, aImport2; + GetImportParam(aImport1); + rData.GetImportParam(aImport2); + return aImport1 == aImport2; +} + +ScDBData::~ScDBData() +{ + StopRefreshTimer(); +} + +OUString ScDBData::GetSourceString() const +{ + if (mpImportParam->bImport) + return mpImportParam->aDBName + "/" + mpImportParam->aStatement; + return OUString(); +} + +OUString ScDBData::GetOperations() const +{ + OUStringBuffer aBuf; + if (mpQueryParam->GetEntryCount()) + { + const ScQueryEntry& rEntry = mpQueryParam->GetEntry(0); + if (rEntry.bDoQuery) + aBuf.append(ScResId(STR_OPERATION_FILTER)); + } + + if (mpSortParam->maKeyState[0].bDoSort) + { + if (!aBuf.isEmpty()) + aBuf.append(", "); + aBuf.append(ScResId(STR_OPERATION_SORT)); + } + + if (mpSubTotal->bGroupActive[0] && !mpSubTotal->bRemoveOnly) + { + if (!aBuf.isEmpty()) + aBuf.append(", "); + aBuf.append(ScResId(STR_OPERATION_SUBTOTAL)); + } + + if (aBuf.isEmpty()) + aBuf.append(ScResId(STR_OPERATION_NONE)); + + return aBuf.makeStringAndClear(); +} + +void ScDBData::GetArea(SCTAB& rTab, SCCOL& rCol1, SCROW& rRow1, SCCOL& rCol2, SCROW& rRow2) const +{ + rTab = nTable; + rCol1 = nStartCol; + rRow1 = nStartRow; + rCol2 = nEndCol; + rRow2 = nEndRow; +} + +void ScDBData::GetArea(ScRange& rRange) const +{ + SCROW nNewEndRow = nEndRow; + rRange = ScRange( nStartCol, nStartRow, nTable, nEndCol, nNewEndRow, nTable ); +} + +ScRange ScDBData::GetHeaderArea() const +{ + if (HasHeader()) + return ScRange( nStartCol, nStartRow, nTable, nEndCol, nStartRow, nTable); + return ScRange( ScAddress::INITIALIZE_INVALID); +} + +void ScDBData::SetArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) +{ + bool bHeaderRangeChange = (nTab != nTable || nCol1 != nStartCol || nCol2 != nEndCol || nRow1 != nStartRow); + if (bHeaderRangeChange) + EndTableColumnNamesListener(); + + nTable = nTab; + nStartCol = nCol1; + nStartRow = nRow1; + nEndCol = nCol2; + nEndRow = nRow2; + + if (bHeaderRangeChange) + { + SAL_WARN_IF( !maTableColumnNames.empty(), "sc.core", "ScDBData::SetArea - invalidating column names/offsets"); + // Invalidate *after* new area has been set above to add the proper + // header range to dirty list. + InvalidateTableColumnNames( true); + StartTableColumnNamesListener(); + } +} + +void ScDBData::MoveTo(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + SCCOL nUpdateCol) +{ + tools::Long nDifX = static_cast<tools::Long>(nCol1) - static_cast<tools::Long>(nStartCol); + tools::Long nDifY = static_cast<tools::Long>(nRow1) - static_cast<tools::Long>(nStartRow); + + tools::Long nSortDif = bByRow ? nDifX : nDifY; + tools::Long nSortEnd = bByRow ? static_cast<tools::Long>(nCol2) : static_cast<tools::Long>(nRow2); + + for (sal_uInt16 i=0; i<mpSortParam->GetSortKeyCount(); i++) + { + mpSortParam->maKeyState[i].nField += nSortDif; + if (mpSortParam->maKeyState[i].nField > nSortEnd) + { + mpSortParam->maKeyState[i].nField = 0; + mpSortParam->maKeyState[i].bDoSort = false; + } + } + + SCSIZE nCount = mpQueryParam->GetEntryCount(); + for (SCSIZE i = 0; i < nCount; ++i) + { + ScQueryEntry& rEntry = mpQueryParam->GetEntry(i); + rEntry.nField += nDifX; + + // tdf#48025, tdf#141946: update the column index of the filter criteria, + // when the deleted/inserted columns are inside the data range + if (nUpdateCol != -1) + { + nUpdateCol += nDifX; + tools::Long nDifX2 + = static_cast<tools::Long>(nCol2) - static_cast<tools::Long>(nEndCol); + if (rEntry.nField >= nUpdateCol) + rEntry.nField += nDifX2; + else if (rEntry.nField >= nUpdateCol + nDifX2) + rEntry.Clear(); + } + + if (rEntry.nField > nCol2) + { + rEntry.nField = 0; + rEntry.bDoQuery = false; + } + } + for (sal_uInt16 i=0; i<MAXSUBTOTAL; i++) + { + mpSubTotal->nField[i] = sal::static_int_cast<SCCOL>( mpSubTotal->nField[i] + nDifX ); + if (mpSubTotal->nField[i] > nCol2) + { + mpSubTotal->nField[i] = 0; + mpSubTotal->bGroupActive[i] = false; + } + } + + SetArea( nTab, nCol1, nRow1, nCol2, nRow2 ); +} + +void ScDBData::GetSortParam( ScSortParam& rSortParam ) const +{ + rSortParam = *mpSortParam; + rSortParam.nCol1 = nStartCol; + rSortParam.nRow1 = nStartRow; + rSortParam.nCol2 = nEndCol; + rSortParam.nRow2 = nEndRow; + rSortParam.bByRow = bByRow; + rSortParam.bHasHeader = bHasHeader; + /* TODO: add Totals to ScSortParam? */ +} + +void ScDBData::SetSortParam( const ScSortParam& rSortParam ) +{ + mpSortParam.reset(new ScSortParam(rSortParam)); + bByRow = rSortParam.bByRow; +} + +void ScDBData::UpdateFromSortParam( const ScSortParam& rSortParam ) +{ + bHasHeader = rSortParam.bHasHeader; +} + +void ScDBData::GetQueryParam( ScQueryParam& rQueryParam ) const +{ + rQueryParam = *mpQueryParam; + rQueryParam.nCol1 = nStartCol; + rQueryParam.nRow1 = nStartRow; + rQueryParam.nCol2 = nEndCol; + rQueryParam.nRow2 = nEndRow; + rQueryParam.nTab = nTable; + rQueryParam.bByRow = bByRow; + rQueryParam.bHasHeader = bHasHeader; + /* TODO: add Totals to ScQueryParam? */ +} + +void ScDBData::SetQueryParam(const ScQueryParam& rQueryParam) +{ + mpQueryParam.reset(new ScQueryParam(rQueryParam)); + + // set bIsAdvanced to false for everything that is not from the + // advanced filter dialog + bIsAdvanced = false; +} + +void ScDBData::SetAdvancedQuerySource(const ScRange* pSource) +{ + if (pSource) + { + aAdvSource = *pSource; + bIsAdvanced = true; + } + else + bIsAdvanced = false; +} + +bool ScDBData::GetAdvancedQuerySource(ScRange& rSource) const +{ + rSource = aAdvSource; + return bIsAdvanced; +} + +void ScDBData::GetSubTotalParam(ScSubTotalParam& rSubTotalParam) const +{ + rSubTotalParam = *mpSubTotal; + + // Share the data range with the parent db data. The range in the subtotal + // param struct is not used. + rSubTotalParam.nCol1 = nStartCol; + rSubTotalParam.nRow1 = nStartRow; + rSubTotalParam.nCol2 = nEndCol; + rSubTotalParam.nRow2 = nEndRow; +} + +void ScDBData::SetSubTotalParam(const ScSubTotalParam& rSubTotalParam) +{ + mpSubTotal.reset(new ScSubTotalParam(rSubTotalParam)); +} + +void ScDBData::GetImportParam(ScImportParam& rImportParam) const +{ + rImportParam = *mpImportParam; + // set the range. + rImportParam.nCol1 = nStartCol; + rImportParam.nRow1 = nStartRow; + rImportParam.nCol2 = nEndCol; + rImportParam.nRow2 = nEndRow; +} + +void ScDBData::SetImportParam(const ScImportParam& rImportParam) +{ + // the range is ignored. + mpImportParam.reset(new ScImportParam(rImportParam)); +} + +bool ScDBData::IsDBAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion) const +{ + if (nTab == nTable) + { + switch (ePortion) + { + case ScDBDataPortion::TOP_LEFT: + return nCol == nStartCol && nRow == nStartRow; + case ScDBDataPortion::AREA: + return nCol >= nStartCol && nCol <= nEndCol && nRow >= nStartRow && nRow <= nEndRow; + } + } + + return false; +} + +bool ScDBData::IsDBAtArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) const +{ + return (nTab == nTable) + && (nCol1 == nStartCol) && (nRow1 == nStartRow) + && (nCol2 == nEndCol) && (nRow2 == nEndRow); +} + +bool ScDBData::HasImportParam() const +{ + return mpImportParam && mpImportParam->bImport; +} + +bool ScDBData::HasQueryParam() const +{ + if (!mpQueryParam) + return false; + + if (!mpQueryParam->GetEntryCount()) + return false; + + return mpQueryParam->GetEntry(0).bDoQuery; +} + +bool ScDBData::HasSortParam() const +{ + return mpSortParam && + !mpSortParam->maKeyState.empty() && + mpSortParam->maKeyState[0].bDoSort; +} + +bool ScDBData::HasSubTotalParam() const +{ + return mpSubTotal && mpSubTotal->bGroupActive[0]; +} + +void ScDBData::UpdateMoveTab(SCTAB nOldPos, SCTAB nNewPos) +{ + ScRange aRange; + GetArea(aRange); + SCTAB nTab = aRange.aStart.Tab(); // a database range is only on one sheet + + // customize as the current table as ScTablesHint (tabvwsh5.cxx) + + if (nTab == nOldPos) // moved sheet + nTab = nNewPos; + else if (nOldPos < nNewPos) // moved to the back + { + if (nTab > nOldPos && nTab <= nNewPos) // move this sheet + --nTab; + } + else // moved to the front + { + if (nTab >= nNewPos && nTab < nOldPos) // move this sheet + ++nTab; + } + + bool bChanged = (nTab != aRange.aStart.Tab()); + if (bChanged) + { + // SetArea() invalidates column names, but it is the same column range + // just on a different sheet; remember and set new. + ::std::vector<OUString> aNames(maTableColumnNames); + bool bTableColumnNamesDirty = mbTableColumnNamesDirty; + // Same column range. + SetArea(nTab, aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Col(), + aRange.aEnd.Row()); + // Do not use SetTableColumnNames() because that resets mbTableColumnNamesDirty. + maTableColumnNames = aNames; + maTableColumnAttributes.resize(aNames.size()); + mbTableColumnNamesDirty = bTableColumnNamesDirty; + } + + // MoveTo() is not necessary if only the sheet changed. + + SetModified(bChanged); +} + +bool ScDBData::UpdateReference(const ScDocument* pDoc, UpdateRefMode eUpdateRefMode, + SCCOL nCol1, SCROW nRow1, SCTAB nTab1, + SCCOL nCol2, SCROW nRow2, SCTAB nTab2, + SCCOL nDx, SCROW nDy, SCTAB nDz) +{ + SCCOL theCol1; + SCROW theRow1; + SCTAB theTab1; + SCCOL theCol2; + SCROW theRow2; + SCTAB theTab2; + GetArea( theTab1, theCol1, theRow1, theCol2, theRow2 ); + theTab2 = theTab1; + SCCOL nOldCol1 = theCol1, nOldCol2 = theCol2; + + ScRefUpdateRes eRet + = ScRefUpdate::Update(pDoc, eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, nDx, + nDy, nDz, theCol1, theRow1, theTab1, theCol2, theRow2, theTab2); + + bool bDoUpdate = eRet != UR_NOTHING; + + if (bDoUpdate && eRet != UR_INVALID) + { + // MoveTo() invalidates column names via SetArea(); adjust, remember and set new. + AdjustTableColumnAttributes( eUpdateRefMode, nDx, nCol1, nOldCol1, nOldCol2, theCol1, theCol2); + ::std::vector<OUString> aNames( maTableColumnNames); + bool bTableColumnNamesDirty = mbTableColumnNamesDirty; + // tdf#48025, tdf#141946: update the column index of the filter criteria, + // when the deleted/inserted columns are inside the data range + if (HasAutoFilter() && theCol1 - nOldCol1 != theCol2 - nOldCol2) + MoveTo(theTab1, theCol1, theRow1, theCol2, theRow2, nCol1); + else + MoveTo( theTab1, theCol1, theRow1, theCol2, theRow2 ); + // Do not use SetTableColumnNames() because that resets mbTableColumnNamesDirty. + maTableColumnNames = aNames; + maTableColumnAttributes.resize(aNames.size()); + mbTableColumnNamesDirty = bTableColumnNamesDirty; + } + + ScRange aRangeAdvSource; + if ( GetAdvancedQuerySource(aRangeAdvSource) ) + { + aRangeAdvSource.GetVars( theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 ); + if ( ScRefUpdate::Update( pDoc, eUpdateRefMode, + nCol1,nRow1,nTab1, nCol2,nRow2,nTab2, nDx,nDy,nDz, + theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 ) ) + { + aRangeAdvSource.aStart.Set( theCol1,theRow1,theTab1 ); + aRangeAdvSource.aEnd.Set( theCol2,theRow2,theTab2 ); + SetAdvancedQuerySource( &aRangeAdvSource ); + + bDoUpdate = true; // DBData is modified + } + } + + SetModified(bDoUpdate); + + return eRet == UR_INVALID; + + //TODO: check if something was deleted/inserted with-in the range !!! +} + +void ScDBData::ExtendDataArea(const ScDocument& rDoc) +{ + // Extend the DB area to include data rows immediately below. + SCCOL nOldCol1 = nStartCol, nOldCol2 = nEndCol; + SCROW nOldEndRow = nEndRow; + rDoc.GetDataArea(nTable, nStartCol, nStartRow, nEndCol, nEndRow, false, true); + // nOldEndRow==rDoc.MaxRow() may easily happen when selecting whole columns and + // setting an AutoFilter (i.e. creating an anonymous database-range). We + // certainly don't want to iterate over nearly a million empty cells, but + // keep only an intentionally user selected range. + if (nOldEndRow < rDoc.MaxRow() && nEndRow < nOldEndRow) + nEndRow = nOldEndRow; + if (nStartCol != nOldCol1 || nEndCol != nOldCol2) + { + SAL_WARN_IF( !maTableColumnNames.empty(), "sc.core", "ScDBData::ExtendDataArea - invalidating column names/offsets"); + InvalidateTableColumnNames( true); + } +} + +void ScDBData::ExtendBackColorArea(const ScDocument& rDoc) +{ + // Extend the DB area to include data rows immediately below. + SCCOL nOldCol1 = nStartCol, nOldCol2 = nEndCol; + SCROW nOldEndRow = nEndRow; + rDoc.GetBackColorArea(nTable, nStartCol, nStartRow, nEndCol, nEndRow); + + if (nOldEndRow < rDoc.MaxRow() && nEndRow < nOldEndRow) + nEndRow = nOldEndRow; + + if (nStartCol != nOldCol1 || nEndCol != nOldCol2) + { + SAL_WARN_IF( !maTableColumnNames.empty(), "sc.core", "ScDBData::ExtendBackColorArea - invalidating column names/offsets"); + InvalidateTableColumnNames( true); + } +} + +void ScDBData::StartTableColumnNamesListener() +{ + if (mpContainer && bHasHeader) + { + ScDocument& rDoc = mpContainer->GetDocument(); + if (!rDoc.IsClipOrUndo()) + rDoc.StartListeningArea( GetHeaderArea(), false, this); + } +} + +void ScDBData::EndTableColumnNamesListener() +{ + EndListeningAll(); +} + +void ScDBData::SetTableColumnNames( ::std::vector< OUString >&& rNames ) +{ + maTableColumnNames = std::move(rNames); + mbTableColumnNamesDirty = false; +} + +void ScDBData::SetTableColumnAttributes( ::std::vector< TableColumnAttributes >&& rAttributes ) +{ + maTableColumnAttributes = std::move(rAttributes); +} + +void ScDBData::AdjustTableColumnAttributes( UpdateRefMode eUpdateRefMode, SCCOL nDx, SCCOL nCol1, + SCCOL nOldCol1, SCCOL nOldCol2, SCCOL nNewCol1, SCCOL nNewCol2 ) +{ + if (maTableColumnNames.empty()) + return; + + SCCOL nDiff1 = nNewCol1 - nOldCol1; + SCCOL nDiff2 = nNewCol2 - nOldCol2; + if (nDiff1 == nDiff2) + return; // not moved or entirely moved, nothing to do + + ::std::vector<OUString> aNewNames; + ::std::vector<TableColumnAttributes> aNewAttributes; + if (eUpdateRefMode == URM_INSDEL) + { + if (nDx > 0) + mbTableColumnNamesDirty = true; // inserted columns will have empty names + + // nCol1 is the first column of the block that gets shifted, determine + // the head and tail elements that are to be copied for deletion or + // insertion. + size_t nHead = static_cast<size_t>(::std::max( nCol1 + std::min<SCCOL>(nDx, 0) - nOldCol1, 0)); + size_t nTail = static_cast<size_t>(::std::max( nOldCol2 - nCol1 + 1, 0)); + size_t n = nHead + nTail; + if (0 < n && n <= maTableColumnNames.size()) + { + if (nDx > 0) + n += nDx; + aNewNames.resize(n); + aNewAttributes.resize(n); + // Copy head. + for (size_t i = 0; i < nHead; ++i) + { + aNewNames[i] = maTableColumnNames[i]; + aNewAttributes[i] = maTableColumnAttributes[i]; + } + // Copy tail, inserted middle range, if any, stays empty. + for (size_t i = n - nTail, j = maTableColumnNames.size() - nTail; i < n; ++i, ++j) + { + aNewNames[i] = maTableColumnNames[j]; + aNewAttributes[i] = maTableColumnAttributes[j]; + } + } + } // else empty aNewNames invalidates names/offsets + + SAL_WARN_IF( !maTableColumnNames.empty() && aNewNames.empty(), + "sc.core", "ScDBData::AdjustTableColumnAttributes - invalidating column attributes/offsets"); + aNewNames.swap( maTableColumnNames); + aNewAttributes.swap(maTableColumnAttributes); + if (maTableColumnNames.empty()) + mbTableColumnNamesDirty = true; + if (mbTableColumnNamesDirty) + InvalidateTableColumnNames( false); // preserve new column names array +} + +void ScDBData::InvalidateTableColumnNames( bool bSwapToEmptyNames ) +{ + mbTableColumnNamesDirty = true; + if (bSwapToEmptyNames && !maTableColumnNames.empty()) + ::std::vector<OUString>().swap( maTableColumnNames); + if (mpContainer) + { + // Add header range to dirty list. + if (HasHeader()) + mpContainer->GetDirtyTableColumnNames().Join( GetHeaderArea()); + else + { + // We need *some* range in the dirty list even without header area, + // otherwise the container would not attempt to call a refresh. + mpContainer->GetDirtyTableColumnNames().Join( ScRange( nStartCol, nStartRow, nTable)); + } + } +} + +namespace { +class TableColumnNameSearch +{ +public: + explicit TableColumnNameSearch( OUString aSearchName ) : + maSearchName(std::move( aSearchName )) + { + } + + bool operator()( const OUString& rName ) const + { + return ScGlobal::GetTransliteration().isEqual( maSearchName, rName); + } + +private: + OUString maSearchName; +}; + +/** Set a numbered table column name at given nIndex, preventing duplicates, + numbering starting at nCount. If nCount==0 then the first attempt is made + with an unnumbered name and if already present the next attempt with + nCount=2, so "Original" and "Original2". No check whether nIndex is valid. */ +void SetTableColumnName( ::std::vector<OUString>& rVec, size_t nIndex, const OUString& rName, size_t nCount ) +{ + OUString aStr; + do + { + if (nCount) + aStr = rName + OUString::number( nCount); + else + { + aStr = rName; + ++nCount; + } + + if (std::none_of( rVec.begin(), rVec.end(), TableColumnNameSearch( aStr))) + { + rVec[nIndex] = aStr; + break; // do while + } + ++nCount; + } while(true); +} +} + +void ScDBData::RefreshTableColumnNames( ScDocument* pDoc ) +{ + ::std::vector<OUString> aNewNames; + aNewNames.resize( nEndCol - nStartCol + 1); + bool bHaveEmpty = false; + if (!HasHeader() || !pDoc) + bHaveEmpty = true; // Assume we have empty ones and fill below. + else + { + ScHorizontalCellIterator aIter(*pDoc, nTable, nStartCol, nStartRow, nEndCol, nStartRow); // header row only + ScRefCellValue* pCell; + SCCOL nCol, nLastColFilled = nStartCol - 1; + SCROW nRow; + while ((pCell = aIter.GetNext( nCol, nRow)) != nullptr) + { + if (pCell->hasString()) + { + const OUString& rStr = pCell->getString( pDoc); + if (rStr.isEmpty()) + bHaveEmpty = true; + else + { + SetTableColumnName( aNewNames, nCol-nStartCol, rStr, 0); + if (nLastColFilled < nCol-1) + bHaveEmpty = true; + } + nLastColFilled = nCol; + } + else + bHaveEmpty = true; + } + } + + // Never leave us with empty names, try to remember previous name that + // might had been used to compile formulas, but only if same number of + // columns and no duplicates. + if (bHaveEmpty && aNewNames.size() == maTableColumnNames.size()) + { + bHaveEmpty = false; + for (size_t i=0, n=aNewNames.size(); i < n; ++i) + { + if (aNewNames[i].isEmpty()) + { + const OUString& rStr = maTableColumnNames[i]; + if (rStr.isEmpty()) + bHaveEmpty = true; + else + SetTableColumnName( aNewNames, i, rStr, 0); + } + } + } + + // If we still have empty ones then fill those with "Column#" with # + // starting at the column offset number. Still no duplicates of course. + if (bHaveEmpty) + { + OUString aColumn( ScResId(STR_COLUMN)); + for (size_t i=0, n=aNewNames.size(); i < n; ++i) + { + if (aNewNames[i].isEmpty()) + SetTableColumnName( aNewNames, i, aColumn, i+1); + } + } + + aNewNames.swap( maTableColumnNames); + maTableColumnAttributes.resize(maTableColumnNames.size()); + mbTableColumnNamesDirty = false; +} + +void ScDBData::RefreshTableColumnNames( ScDocument* pDoc, const ScRange& rRange ) +{ + // Header-less tables get names generated, completely empty a full refresh. + if (mbTableColumnNamesDirty && (!HasHeader() || maTableColumnNames.empty())) + { + RefreshTableColumnNames( pDoc); + return; + } + + // Check if this is affected for the range requested. + ScRange aIntersection( GetHeaderArea().Intersection( rRange)); + if (!aIntersection.IsValid()) + return; + + // Always fully refresh, only one cell of a range was broadcasted per area + // listener if multiple cells were affected. We don't know if there were + // more. Also, we need the full check anyway in case a duplicated name was + // entered. + RefreshTableColumnNames( pDoc); +} + +sal_Int32 ScDBData::GetColumnNameOffset( const OUString& rName ) const +{ + if (maTableColumnNames.empty()) + return -1; + + ::std::vector<OUString>::const_iterator it( + ::std::find_if( maTableColumnNames.begin(), maTableColumnNames.end(), TableColumnNameSearch( rName))); + if (it != maTableColumnNames.end()) + return it - maTableColumnNames.begin(); + + return -1; +} + +OUString ScDBData::GetTableColumnName( SCCOL nCol ) const +{ + if (maTableColumnNames.empty()) + return OUString(); + + SCCOL nOffset = nCol - nStartCol; + if (nOffset < 0 || maTableColumnNames.size() <= o3tl::make_unsigned(nOffset)) + return OUString(); + + return maTableColumnNames[nOffset]; +} + +void ScDBData::Notify( const SfxHint& rHint ) +{ + if (rHint.GetId() != SfxHintId::ScDataChanged) + return; + const ScHint* pScHint = static_cast<const ScHint*>(&rHint); + + mbTableColumnNamesDirty = true; + if (!mpContainer) + assert(!"ScDBData::Notify - how did we end up here without container?"); + else + { + // Only one cell of a range is broadcasted per area listener if + // multiple cells are affected. Expand the range to what this is + // listening to. Broadcasted address outside should not happen, + // but... let it trigger a refresh if. + const ScRange aHeaderRange( GetHeaderArea()); + ScAddress aHintAddress( pScHint->GetStartAddress()); + if (aHeaderRange.IsValid()) + { + mpContainer->GetDirtyTableColumnNames().Join( aHeaderRange); + // Header range is one row. + // The ScHint's "range" is an address with row count. + // Though broadcasted is usually only one cell, check for the + // possible case of row block and for one cell in the same row. + if (aHintAddress.Row() <= aHeaderRange.aStart.Row() + && aHeaderRange.aStart.Row() < aHintAddress.Row() + pScHint->GetRowCount()) + { + aHintAddress.SetRow( aHeaderRange.aStart.Row()); + if (!aHeaderRange.Contains( aHintAddress)) + mpContainer->GetDirtyTableColumnNames().Join( aHintAddress); + } + } + else + { + // We need *some* range in the dirty list even without header area, + // otherwise the container would not attempt to call a refresh. + aHintAddress.SetRow( nStartRow); + mpContainer->GetDirtyTableColumnNames().Join( aHintAddress); + } + } + + // Do not refresh column names here, which might trigger unwanted + // recalculation. +} + +void ScDBData::CalcSaveFilteredCount( SCSIZE nNonFilteredRowCount ) +{ + SCSIZE nTotal = nEndRow - nStartRow + 1; + if ( bHasHeader ) + nTotal -= 1; + nFilteredRowCount = nTotal - nNonFilteredRowCount; +} + +void ScDBData::GetFilterSelCount( SCSIZE& nSelected, SCSIZE& nTotal ) +{ + nTotal = nEndRow - nStartRow + 1; + if ( bHasHeader ) + nTotal -= 1; + if( nFilteredRowCount != SCSIZE_MAX ) + nSelected = nTotal - nFilteredRowCount; + else + nSelected = nFilteredRowCount; +} + +namespace { + +class FindByTable +{ + SCTAB mnTab; +public: + explicit FindByTable(SCTAB nTab) : mnTab(nTab) {} + + bool operator() (std::unique_ptr<ScDBData> const& p) const + { + ScRange aRange; + p->GetArea(aRange); + return aRange.aStart.Tab() == mnTab; + } +}; + +class UpdateMoveTabFunc +{ + SCTAB mnOldTab; + SCTAB mnNewTab; +public: + UpdateMoveTabFunc(SCTAB nOld, SCTAB nNew) : mnOldTab(nOld), mnNewTab(nNew) {} + void operator() (std::unique_ptr<ScDBData> const& p) + { + p->UpdateMoveTab(mnOldTab, mnNewTab); + } +}; + +OUString lcl_IncrementNumberInNamedRange(ScDBCollection::NamedDBs& namedDBs, + std::u16string_view rOldName) +{ + // Append or increment a numeric suffix and do not generate names that + // could result in a cell reference by ensuring at least one underscore is + // present. + // "aa" => "aa_2" + // "aaaa1" => "aaaa1_2" + // "aa_a" => "aa_a_2" + // "aa_a_" => "aa_a__2" + // "aa_a1" => "aa_a1_2" + // "aa_1a" => "aa_1a_2" + // "aa_1" => "aa_2" + // "aa_2" => "aa_3" + + size_t nLastIndex = rOldName.rfind('_'); + sal_Int32 nOldNumber = 1; + OUString aPrefix; + if (nLastIndex != std::u16string_view::npos) + { + ++nLastIndex; + std::u16string_view sLastPart(rOldName.substr(nLastIndex)); + nOldNumber = o3tl::toInt32(sLastPart); + + // If that number is exactly at the end then increment the number; else + // append "_" and number. + // toInt32() returns 0 on failure and also stops at trailing non-digit + // characters (toInt32("1a")==1). + if (OUString::number(nOldNumber) == sLastPart) + aPrefix = rOldName.substr(0, nLastIndex); + else + { + aPrefix = OUString::Concat(rOldName) + "_"; + nOldNumber = 1; + } + } + else // No "_" found, append "_" and number. + aPrefix = OUString::Concat(rOldName) + "_"; + OUString sNewName; + do + { + sNewName = aPrefix + OUString::number(++nOldNumber); + } while (namedDBs.findByName(sNewName) != nullptr); + return sNewName; +} + +class FindByCursor +{ + SCCOL mnCol; + SCROW mnRow; + SCTAB mnTab; + ScDBDataPortion mePortion; +public: + FindByCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion) : + mnCol(nCol), mnRow(nRow), mnTab(nTab), mePortion(ePortion) {} + + bool operator() (std::unique_ptr<ScDBData> const& p) + { + return p->IsDBAtCursor(mnCol, mnRow, mnTab, mePortion); + } +}; + +class FindByRange +{ + const ScRange& mrRange; +public: + explicit FindByRange(const ScRange& rRange) : mrRange(rRange) {} + + bool operator() (std::unique_ptr<ScDBData> const& p) + { + return p->IsDBAtArea( + mrRange.aStart.Tab(), mrRange.aStart.Col(), mrRange.aStart.Row(), mrRange.aEnd.Col(), mrRange.aEnd.Row()); + } +}; + +class FindByIndex +{ + sal_uInt16 mnIndex; +public: + explicit FindByIndex(sal_uInt16 nIndex) : mnIndex(nIndex) {} + bool operator() (std::unique_ptr<ScDBData> const& p) const + { + return p->GetIndex() == mnIndex; + } +}; + +class FindByUpperName +{ + const OUString& mrName; +public: + explicit FindByUpperName(const OUString& rName) : mrName(rName) {} + bool operator() (std::unique_ptr<ScDBData> const& p) const + { + return p->GetUpperName() == mrName; + } +}; + +class FindByName +{ + const OUString& mrName; +public: + explicit FindByName(const OUString& rName) : mrName(rName) {} + bool operator() (std::unique_ptr<ScDBData> const& p) const + { + return p->GetName() == mrName; + } +}; + +class FindByPointer +{ + const ScDBData* mpDBData; +public: + explicit FindByPointer(const ScDBData* pDBData) : mpDBData(pDBData) {} + bool operator() (std::unique_ptr<ScDBData> const& p) const + { + return p.get() == mpDBData; + } +}; + +} + +ScDocument& ScDBDataContainerBase::GetDocument() const +{ + return mrDoc; +} + +ScRangeList& ScDBDataContainerBase::GetDirtyTableColumnNames() +{ + return maDirtyTableColumnNames; +} + +ScDBCollection::NamedDBs::NamedDBs(ScDBCollection& rParent, ScDocument& rDoc) : + ScDBDataContainerBase(rDoc), mrParent(rParent) {} + +ScDBCollection::NamedDBs::NamedDBs(const NamedDBs& r, ScDBCollection& rParent) + : ScDBDataContainerBase(r.mrDoc) + , mrParent(rParent) +{ + for (auto const& it : r.m_DBs) + { + ScDBData* p = new ScDBData(*it); + std::unique_ptr<ScDBData> pData(p); + if (m_DBs.insert( std::move(pData)).second) + initInserted(p); + } +} + +ScDBCollection::NamedDBs::~NamedDBs() +{ +} + +void ScDBCollection::NamedDBs::initInserted( ScDBData* p ) +{ + p->SetContainer( this); + if (mrDoc.IsClipOrUndo()) + return; + + p->StartTableColumnNamesListener(); // needs the container be set already + if (!p->AreTableColumnNamesDirty()) + return; + + if (p->HasHeader()) + { + // Refresh table column names in next round. + maDirtyTableColumnNames.Join( p->GetHeaderArea()); + } + else + { + // Header-less table can generate its column names + // already without accessing the document. + p->RefreshTableColumnNames( nullptr); + } +} + +ScDBCollection::NamedDBs::iterator ScDBCollection::NamedDBs::begin() +{ + return m_DBs.begin(); +} + +ScDBCollection::NamedDBs::iterator ScDBCollection::NamedDBs::end() +{ + return m_DBs.end(); +} + +ScDBCollection::NamedDBs::const_iterator ScDBCollection::NamedDBs::begin() const +{ + return m_DBs.begin(); +} + +ScDBCollection::NamedDBs::const_iterator ScDBCollection::NamedDBs::end() const +{ + return m_DBs.end(); +} + +ScDBData* ScDBCollection::NamedDBs::findByIndex(sal_uInt16 nIndex) +{ + DBsType::iterator itr = find_if( + m_DBs.begin(), m_DBs.end(), FindByIndex(nIndex)); + return itr == m_DBs.end() ? nullptr : itr->get(); +} + +ScDBData* ScDBCollection::NamedDBs::findByUpperName(const OUString& rName) +{ + DBsType::iterator itr = find_if( + m_DBs.begin(), m_DBs.end(), FindByUpperName(rName)); + return itr == m_DBs.end() ? nullptr : itr->get(); +} + +auto ScDBCollection::NamedDBs::findByUpperName2(const OUString& rName) -> iterator +{ + return find_if( + m_DBs.begin(), m_DBs.end(), FindByUpperName(rName)); +} + +ScDBData* ScDBCollection::NamedDBs::findByName(const OUString& rName) +{ + DBsType::iterator itr = find_if(m_DBs.begin(), m_DBs.end(), FindByName(rName)); + return itr == m_DBs.end() ? nullptr : itr->get(); +} + +bool ScDBCollection::NamedDBs::insert(std::unique_ptr<ScDBData> pData) +{ + auto p = pData.get(); + if (!pData->GetIndex()) + pData->SetIndex(mrParent.nEntryIndex++); + + std::pair<DBsType::iterator, bool> r = m_DBs.insert(std::move(pData)); + + if (r.second) + { + initInserted(p); + + /* TODO: shouldn't the import refresh not be setup for + * clipboard/undo documents? It was already like this before... */ + if (p->HasImportParam() && !p->HasImportSelection()) + { + p->SetRefreshHandler(mrParent.GetRefreshHandler()); + p->SetRefreshControl(&mrDoc.GetRefreshTimerControlAddress()); + } + } + return r.second; +} + +ScDBCollection::NamedDBs::iterator ScDBCollection::NamedDBs::erase(const iterator& itr) +{ + return m_DBs.erase(itr); +} + +bool ScDBCollection::NamedDBs::empty() const +{ + return m_DBs.empty(); +} + +size_t ScDBCollection::NamedDBs::size() const +{ + return m_DBs.size(); +} + +bool ScDBCollection::NamedDBs::operator== (const NamedDBs& r) const +{ + return ::comphelper::ContainerUniquePtrEquals(m_DBs, r.m_DBs); +} + +ScDBCollection::AnonDBs::iterator ScDBCollection::AnonDBs::begin() +{ + return m_DBs.begin(); +} + +ScDBCollection::AnonDBs::iterator ScDBCollection::AnonDBs::end() +{ + return m_DBs.end(); +} + +ScDBCollection::AnonDBs::const_iterator ScDBCollection::AnonDBs::begin() const +{ + return m_DBs.begin(); +} + +ScDBCollection::AnonDBs::const_iterator ScDBCollection::AnonDBs::end() const +{ + return m_DBs.end(); +} + +const ScDBData* ScDBCollection::AnonDBs::findAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, + ScDBDataPortion ePortion) const +{ + DBsType::const_iterator itr = find_if( + m_DBs.begin(), m_DBs.end(), FindByCursor(nCol, nRow, nTab, ePortion)); + return itr == m_DBs.end() ? nullptr : itr->get(); +} + +const ScDBData* ScDBCollection::AnonDBs::findByRange(const ScRange& rRange) const +{ + DBsType::const_iterator itr = find_if( + m_DBs.begin(), m_DBs.end(), FindByRange(rRange)); + return itr == m_DBs.end() ? nullptr : itr->get(); +} + +void ScDBCollection::AnonDBs::deleteOnTab(SCTAB nTab) +{ + FindByTable func(nTab); + std::erase_if(m_DBs, func); +} + +ScDBData* ScDBCollection::AnonDBs::getByRange(const ScRange& rRange) +{ + const ScDBData* pData = findByRange(rRange); + if (!pData) + { + // Insert a new db data. They all have identical names. + ::std::unique_ptr<ScDBData> pNew(new ScDBData( + STR_DB_GLOBAL_NONAME, rRange.aStart.Tab(), rRange.aStart.Col(), rRange.aStart.Row(), + rRange.aEnd.Col(), rRange.aEnd.Row(), true, false, false)); + pData = pNew.get(); + m_DBs.push_back(std::move(pNew)); + } + return const_cast<ScDBData*>(pData); +} + +void ScDBCollection::AnonDBs::insert(ScDBData* p) +{ + m_DBs.push_back(std::unique_ptr<ScDBData>(p)); +} + +ScDBCollection::AnonDBs::iterator ScDBCollection::AnonDBs::erase(const iterator& itr) +{ + return m_DBs.erase(itr); +} + +bool ScDBCollection::AnonDBs::empty() const +{ + return m_DBs.empty(); +} + +bool ScDBCollection::AnonDBs::has( const ScDBData* p ) const +{ + return any_of(m_DBs.begin(), m_DBs.end(), FindByPointer(p)); +} + +bool ScDBCollection::AnonDBs::operator== (const AnonDBs& r) const +{ + return ::comphelper::ContainerUniquePtrEquals(m_DBs, r.m_DBs); +} + +ScDBCollection::AnonDBs::AnonDBs() +{ +} + +ScDBCollection::AnonDBs::AnonDBs(AnonDBs const& r) +{ + m_DBs.reserve(r.m_DBs.size()); + for (auto const& it : r.m_DBs) + { + m_DBs.push_back(std::make_unique<ScDBData>(*it)); + } +} + +ScDBCollection::ScDBCollection(ScDocument& rDocument) : + rDoc(rDocument), nEntryIndex(1), maNamedDBs(*this, rDocument) {} + +ScDBCollection::ScDBCollection(const ScDBCollection& r) : + rDoc(r.rDoc), nEntryIndex(r.nEntryIndex), maNamedDBs(r.maNamedDBs, *this), maAnonDBs(r.maAnonDBs) {} + +const ScDBData* ScDBCollection::GetDBAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion) const +{ + // First, search the global named db ranges. + NamedDBs::DBsType::const_iterator itr = find_if( + maNamedDBs.begin(), maNamedDBs.end(), FindByCursor(nCol, nRow, nTab, ePortion)); + if (itr != maNamedDBs.end()) + return itr->get(); + + // Check for the sheet-local anonymous db range. + const ScDBData* pNoNameData = rDoc.GetAnonymousDBData(nTab); + if (pNoNameData) + if (pNoNameData->IsDBAtCursor(nCol,nRow,nTab,ePortion)) + return pNoNameData; + + // Check the global anonymous db ranges. + const ScDBData* pData = getAnonDBs().findAtCursor(nCol, nRow, nTab, ePortion); + if (pData) + return pData; + + // Do NOT check for the document global temporary anonymous db range here. + + return nullptr; +} + +ScDBData* ScDBCollection::GetDBAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion) +{ + // First, search the global named db ranges. + NamedDBs::DBsType::iterator itr = find_if( + maNamedDBs.begin(), maNamedDBs.end(), FindByCursor(nCol, nRow, nTab, ePortion)); + if (itr != maNamedDBs.end()) + return itr->get(); + + // Check for the sheet-local anonymous db range. + ScDBData* pNoNameData = rDoc.GetAnonymousDBData(nTab); + if (pNoNameData) + if (pNoNameData->IsDBAtCursor(nCol,nRow,nTab,ePortion)) + return pNoNameData; + + // Check the global anonymous db ranges. + const ScDBData* pData = getAnonDBs().findAtCursor(nCol, nRow, nTab, ePortion); + if (pData) + return const_cast<ScDBData*>(pData); + + // Do NOT check for the document global temporary anonymous db range here. + + return nullptr; +} + +const ScDBData* ScDBCollection::GetDBAtArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) const +{ + // First, search the global named db ranges. + ScRange aRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab); + NamedDBs::DBsType::const_iterator itr = find_if( + maNamedDBs.begin(), maNamedDBs.end(), FindByRange(aRange)); + if (itr != maNamedDBs.end()) + return itr->get(); + + // Check for the sheet-local anonymous db range. + ScDBData* pNoNameData = rDoc.GetAnonymousDBData(nTab); + if (pNoNameData) + if (pNoNameData->IsDBAtArea(nTab, nCol1, nRow1, nCol2, nRow2)) + return pNoNameData; + + // Lastly, check the global anonymous db ranges. + const ScDBData* pData = maAnonDBs.findByRange(aRange); + if (pData) + return pData; + + // As a last resort, check for the document global temporary anonymous db range. + pNoNameData = rDoc.GetAnonymousDBData(); + if (pNoNameData) + if (pNoNameData->IsDBAtArea(nTab, nCol1, nRow1, nCol2, nRow2)) + return pNoNameData; + + return nullptr; +} + +ScDBData* ScDBCollection::GetDBAtArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) +{ + // First, search the global named db ranges. + ScRange aRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab); + NamedDBs::DBsType::iterator itr = find_if( + maNamedDBs.begin(), maNamedDBs.end(), FindByRange(aRange)); + if (itr != maNamedDBs.end()) + return itr->get(); + + // Check for the sheet-local anonymous db range. + ScDBData* pNoNameData = rDoc.GetAnonymousDBData(nTab); + if (pNoNameData) + if (pNoNameData->IsDBAtArea(nTab, nCol1, nRow1, nCol2, nRow2)) + return pNoNameData; + + // Lastly, check the global anonymous db ranges. + const ScDBData* pData = getAnonDBs().findByRange(aRange); + if (pData) + return const_cast<ScDBData*>(pData); + + // As a last resort, check for the document global temporary anonymous db range. + pNoNameData = rDoc.GetAnonymousDBData(); + if (pNoNameData) + if (pNoNameData->IsDBAtArea(nTab, nCol1, nRow1, nCol2, nRow2)) + return pNoNameData; + + return nullptr; +} + +void ScDBCollection::RefreshDirtyTableColumnNames() +{ + for (size_t i=0; i < maNamedDBs.maDirtyTableColumnNames.size(); ++i) + { + const ScRange & rRange = maNamedDBs.maDirtyTableColumnNames[i]; + for (auto const& it : maNamedDBs) + { + if (it->AreTableColumnNamesDirty()) + it->RefreshTableColumnNames( &maNamedDBs.mrDoc, rRange); + } + } + maNamedDBs.maDirtyTableColumnNames.RemoveAll(); +} + +void ScDBCollection::DeleteOnTab( SCTAB nTab ) +{ + FindByTable func(nTab); + // First, collect the positions of all items that need to be deleted. + ::std::vector<NamedDBs::DBsType::iterator> v; + { + NamedDBs::DBsType::iterator itr = maNamedDBs.begin(), itrEnd = maNamedDBs.end(); + for (; itr != itrEnd; ++itr) + { + if (func(*itr)) + v.push_back(itr); + } + } + + // Delete them all. + for (const auto& rIter : v) + maNamedDBs.erase(rIter); + + maAnonDBs.deleteOnTab(nTab); +} + +void ScDBCollection::UpdateReference(UpdateRefMode eUpdateRefMode, + SCCOL nCol1, SCROW nRow1, SCTAB nTab1, + SCCOL nCol2, SCROW nRow2, SCTAB nTab2, + SCCOL nDx, SCROW nDy, SCTAB nDz ) +{ + ScDBData* pData = rDoc.GetAnonymousDBData(nTab1); + if (pData) + { + if (nTab1 == nTab2 && nDz == 0) + { + // Delete the database range, if some part of the reference became invalid. + if (pData->UpdateReference(&rDoc, eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, + nTab2, nDx, nDy, nDz)) + rDoc.SetAnonymousDBData(nTab1, nullptr); + } + else + { + //this will perhaps break undo + } + } + + for (auto it = maNamedDBs.begin(); it != maNamedDBs.end(); ) + { + // Delete the database range, if some part of the reference became invalid. + if (it->get()->UpdateReference(&rDoc, eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, + nTab2, nDx, nDy, nDz)) + it = maNamedDBs.erase(it); + else + ++it; + } + for (auto it = maAnonDBs.begin(); it != maAnonDBs.end(); ) + { + // Delete the database range, if some part of the reference became invalid. + if (it->get()->UpdateReference(&rDoc, eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, + nTab2, nDx, nDy, nDz)) + it = maAnonDBs.erase(it); + else + ++it; + } +} + +void ScDBCollection::UpdateMoveTab( SCTAB nOldPos, SCTAB nNewPos ) +{ + UpdateMoveTabFunc func(nOldPos, nNewPos); + for_each(maNamedDBs.begin(), maNamedDBs.end(), func); + for_each(maAnonDBs.begin(), maAnonDBs.end(), func); +} + +void ScDBCollection::CopyToTable(SCTAB nOldPos, SCTAB nNewPos) +{ + // Create temporary copy of pointers to not insert in a set we are + // iterating over. + std::vector<const ScDBData*> aTemp; + aTemp.reserve( maNamedDBs.size()); + for (const auto& rxNamedDB : maNamedDBs) + { + if (rxNamedDB->GetTab() != nOldPos) + continue; + aTemp.emplace_back( rxNamedDB.get()); + } + for (const auto& rxNamedDB : aTemp) + { + const OUString newName( lcl_IncrementNumberInNamedRange( maNamedDBs, rxNamedDB->GetName())); + std::unique_ptr<ScDBData> pDataCopy = std::make_unique<ScDBData>(newName, *rxNamedDB); + pDataCopy->UpdateMoveTab(nOldPos, nNewPos); + pDataCopy->SetIndex(0); + maNamedDBs.insert(std::move(pDataCopy)); + } +} + +ScDBData* ScDBCollection::GetDBNearCursor(SCCOL nCol, SCROW nRow, SCTAB nTab ) +{ + ScDBData* pNearData = nullptr; + for (const auto& rxNamedDB : maNamedDBs) + { + SCTAB nAreaTab; + SCCOL nStartCol, nEndCol; + SCROW nStartRow, nEndRow; + rxNamedDB->GetArea( nAreaTab, nStartCol, nStartRow, nEndCol, nEndRow ); + if ( nTab == nAreaTab && nCol+1 >= nStartCol && nCol <= nEndCol+1 && + nRow+1 >= nStartRow && nRow <= nEndRow+1 ) + { + if ( nCol < nStartCol || nCol > nEndCol || nRow < nStartRow || nRow > nEndRow ) + { + if (!pNearData) + pNearData = rxNamedDB.get(); // remember first adjacent area + } + else + return rxNamedDB.get(); // not "unbenannt"/"unnamed" and cursor within + } + } + if (pNearData) + return pNearData; // adjacent, if no direct hit + return rDoc.GetAnonymousDBData(nTab); // "unbenannt"/"unnamed" only if nothing else +} + +std::vector<ScDBData*> ScDBCollection::GetAllDBsFromTab(SCTAB nTab) +{ + std::vector<ScDBData*> pTabData; + for (const auto& rxNamedDB : maNamedDBs) + { + if (rxNamedDB->GetTab() == nTab) + pTabData.emplace_back(rxNamedDB.get()); + } + auto pAnonDBData = rDoc.GetAnonymousDBData(nTab); + if (pAnonDBData) + pTabData.emplace_back(pAnonDBData); + return pTabData; +} + +bool ScDBCollection::empty() const +{ + return maNamedDBs.empty() && maAnonDBs.empty(); +} + +bool ScDBCollection::operator== (const ScDBCollection& r) const +{ + return maNamedDBs == r.maNamedDBs && maAnonDBs == r.maAnonDBs && + nEntryIndex == r.nEntryIndex && &rDoc == &r.rDoc && aRefreshHandler == r.aRefreshHandler; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/ddelink.cxx b/sc/source/core/tool/ddelink.cxx new file mode 100644 index 0000000000..5000e0bf87 --- /dev/null +++ b/sc/source/core/tool/ddelink.cxx @@ -0,0 +1,266 @@ +/* -*- 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 <comphelper/fileformat.h> +#include <comphelper/string.hxx> +#include <osl/thread.h> +#include <sot/exchange.hxx> +#include <sfx2/linkmgr.hxx> +#include <sfx2/bindings.hxx> +#include <svl/numformat.hxx> +#include <svl/sharedstringpool.hxx> +#include <o3tl/string_view.hxx> + +#include <ddelink.hxx> +#include <brdcst.hxx> +#include <document.hxx> +#include <scmatrix.hxx> +#include <patattr.hxx> +#include <rechead.hxx> +#include <rangeseq.hxx> +#include <sc.hrc> +#include <hints.hxx> +#include <utility> + + +bool ScDdeLink::bIsInUpdate = false; + +ScDdeLink::ScDdeLink( ScDocument& rD, OUString aA, OUString aT, OUString aI, + sal_uInt8 nM ) : + ::sfx2::SvBaseLink(SfxLinkUpdateMode::ALWAYS,SotClipboardFormatId::STRING), + rDoc( rD ), + aAppl(std::move( aA )), + aTopic(std::move( aT )), + aItem(std::move( aI )), + nMode( nM ), + bNeedUpdate( false ), + pResult( nullptr ) +{ +} + +ScDdeLink::~ScDdeLink() +{ + // cancel connection + + // pResult is refcounted +} + +ScDdeLink::ScDdeLink( ScDocument& rD, const ScDdeLink& rOther ) : + ::sfx2::SvBaseLink(SfxLinkUpdateMode::ALWAYS,SotClipboardFormatId::STRING), + rDoc ( rD ), + aAppl ( rOther.aAppl ), + aTopic ( rOther.aTopic ), + aItem ( rOther.aItem ), + nMode ( rOther.nMode ), + bNeedUpdate( false ), + pResult ( nullptr ) +{ + if (rOther.pResult) + pResult = rOther.pResult->Clone(); +} + +ScDdeLink::ScDdeLink( ScDocument& rD, SvStream& rStream, ScMultipleReadHeader& rHdr ) : + ::sfx2::SvBaseLink(SfxLinkUpdateMode::ALWAYS,SotClipboardFormatId::STRING), + rDoc( rD ), + bNeedUpdate( false ), + pResult( nullptr ) +{ + rHdr.StartEntry(); + + rtl_TextEncoding eCharSet = rStream.GetStreamCharSet(); + aAppl = rStream.ReadUniOrByteString( eCharSet ); + aTopic = rStream.ReadUniOrByteString( eCharSet ); + aItem = rStream.ReadUniOrByteString( eCharSet ); + + bool bHasValue; + rStream.ReadCharAsBool( bHasValue ); + if ( bHasValue ) + pResult = new ScMatrix(0, 0); + + if (rHdr.BytesLeft()) // new in 388b and the 364w (RealTime Client) version + rStream.ReadUChar( nMode ); + else + nMode = SC_DDE_DEFAULT; + + rHdr.EndEntry(); +} + +void ScDdeLink::Store( SvStream& rStream, ScMultipleWriteHeader& rHdr ) const +{ + rHdr.StartEntry(); + + rtl_TextEncoding eCharSet = rStream.GetStreamCharSet(); + rStream.WriteUniOrByteString( aAppl, eCharSet ); + rStream.WriteUniOrByteString( aTopic, eCharSet ); + rStream.WriteUniOrByteString( aItem, eCharSet ); + + bool bHasValue = ( pResult != nullptr ); + rStream.WriteBool( bHasValue ); + + if( rStream.GetVersion() > SOFFICE_FILEFORMAT_40 ) // not with 4.0 Export + rStream.WriteUChar( nMode ); // since 388b + + // links with Mode != SC_DDE_DEFAULT are completely omitted in 4.0 Export + // (from ScDocument::SaveDdeLinks) + + rHdr.EndEntry(); +} + +sfx2::SvBaseLink::UpdateResult ScDdeLink::DataChanged( + const OUString& rMimeType, const css::uno::Any & rValue ) +{ + // we only master strings... + if ( SotClipboardFormatId::STRING != SotExchange::GetFormatIdFromMimeType( rMimeType )) + return SUCCESS; + + OUString aLinkStr; + ScByteSequenceToString::GetString( aLinkStr, rValue ); + aLinkStr = convertLineEnd(aLinkStr, LINEEND_LF); + + // if string ends with line end, discard: + + sal_Int32 nLen = aLinkStr.getLength(); + if (nLen && aLinkStr[nLen-1] == '\n') + aLinkStr = aLinkStr.copy(0, nLen-1); + + SCSIZE nCols = 1; // empty string -> an empty line + SCSIZE nRows = 1; + if (!aLinkStr.isEmpty()) + { + nRows = static_cast<SCSIZE>(comphelper::string::getTokenCount(aLinkStr, '\n')); + std::u16string_view aLine = o3tl::getToken(aLinkStr, 0, '\n' ); + if (!aLine.empty()) + nCols = static_cast<SCSIZE>(comphelper::string::getTokenCount(aLine, '\t')); + } + + if (!nRows || !nCols) // no data + { + pResult.reset(); + } + else // split data + { + // always newly re-create matrix, so that bIsString doesn't get mixed up + pResult = new ScMatrix(nCols, nRows, 0.0); + + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + svl::SharedStringPool& rPool = rDoc.GetSharedStringPool(); + + // nMode determines how the text is interpreted (#44455#/#49783#): + // SC_DDE_DEFAULT - number format from cell template "Standard" + // SC_DDE_ENGLISH - standard number format for English/US + // SC_DDE_TEXT - without NumberFormatter directly as string + sal_uLong nStdFormat = 0; + if ( nMode == SC_DDE_DEFAULT ) + { + ScPatternAttr* pDefPattern = rDoc.GetDefPattern(); // contains standard template + if ( pDefPattern ) + nStdFormat = pDefPattern->GetNumberFormat( pFormatter ); + } + else if ( nMode == SC_DDE_ENGLISH ) + nStdFormat = pFormatter->GetStandardIndex(LANGUAGE_ENGLISH_US); + + for (SCSIZE nR=0; nR<nRows; nR++) + { + std::u16string_view aLine = o3tl::getToken(aLinkStr, static_cast<sal_Int32>(nR), '\n' ); + for (SCSIZE nC=0; nC<nCols; nC++) + { + OUString aEntry( o3tl::getToken(aLine, static_cast<sal_Int32>(nC), '\t' ) ); + sal_uInt32 nIndex = nStdFormat; + double fVal = double(); + if ( nMode != SC_DDE_TEXT && pFormatter->IsNumberFormat( aEntry, nIndex, fVal ) ) + pResult->PutDouble( fVal, nC, nR ); + else if (aEntry.isEmpty()) + // empty cell + pResult->PutEmpty(nC, nR); + else + pResult->PutString(rPool.intern(aEntry), nC, nR); + } + } + } + + // Something happened... + + if (HasListeners()) + { + Broadcast(ScHint(SfxHintId::ScDataChanged, ScAddress())); + rDoc.TrackFormulas(); // must happen immediately + rDoc.StartTrackTimer(); + + // StartTrackTimer asynchronously calls TrackFormulas, Broadcast(FID_DATACHANGED), + // ResetChanged, SetModified and Invalidate(SID_SAVEDOC/SID_DOC_MODIFIED) + // TrackFormulas additionally once again immediately, so that, e.g., a formula still + // located in the FormulaTrack doesn't get calculated by IdleCalc (#61676#) + + // notify Uno objects (for XRefreshListener) + // must be after TrackFormulas + //TODO: do this asynchronously? + ScLinkRefreshedHint aHint; + aHint.SetDdeLink( aAppl, aTopic, aItem ); + rDoc.BroadcastUno( aHint ); + } + + return SUCCESS; +} + +void ScDdeLink::ListenersGone() +{ + bool bWas = bIsInUpdate; + bIsInUpdate = true; // Remove() can trigger reschedule??!? + + ScDocument& rStackDoc = rDoc; // member rDoc can't be used after removing the link + + sfx2::LinkManager* pLinkMgr = rDoc.GetLinkManager(); + pLinkMgr->Remove( this); // deletes this + + if ( pLinkMgr->GetLinks().empty() ) // deleted the last one ? + { + SfxBindings* pBindings = rStackDoc.GetViewBindings(); // don't use member rDoc! + if (pBindings) + pBindings->Invalidate( SID_LINKS ); + } + + bIsInUpdate = bWas; +} + +const ScMatrix* ScDdeLink::GetResult() const +{ + return pResult.get(); +} + +void ScDdeLink::SetResult( const ScMatrixRef& pRes ) +{ + pResult = pRes; +} + +void ScDdeLink::TryUpdate() +{ + if (bIsInUpdate) + bNeedUpdate = true; // cannot be executed now + else + { + bIsInUpdate = true; + rDoc.IncInDdeLinkUpdate(); + Update(); + rDoc.DecInDdeLinkUpdate(); + bIsInUpdate = false; + bNeedUpdate = false; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/defaultsoptions.cxx b/sc/source/core/tool/defaultsoptions.cxx new file mode 100644 index 0000000000..a55f154fef --- /dev/null +++ b/sc/source/core/tool/defaultsoptions.cxx @@ -0,0 +1,152 @@ +/* -*- 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/. + */ + +#include <com/sun/star/uno/Sequence.hxx> +#include <osl/diagnose.h> + +#include <defaultsoptions.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <sc.hrc> +#include <utility> + +using namespace utl; +using namespace com::sun::star::uno; + + +ScDefaultsOptions::ScDefaultsOptions() +{ + SetDefaults(); +} + +void ScDefaultsOptions::SetDefaults() +{ + nInitTabCount = 1; + aInitTabPrefix = ScResId(STR_TABLE_DEF); // Default Prefix "Sheet" + bJumboSheets = false; +} + +bool ScDefaultsOptions::operator==( const ScDefaultsOptions& rOpt ) const +{ + return rOpt.nInitTabCount == nInitTabCount + && rOpt.aInitTabPrefix == aInitTabPrefix + && rOpt.bJumboSheets == bJumboSheets; +} + +ScTpDefaultsItem::ScTpDefaultsItem( ScDefaultsOptions aOpt ) : + SfxPoolItem ( SID_SCDEFAULTSOPTIONS ), + theOptions (std::move( aOpt )) +{ +} + +ScTpDefaultsItem::~ScTpDefaultsItem() +{ +} + +bool ScTpDefaultsItem::operator==( const SfxPoolItem& rItem ) const +{ + assert(SfxPoolItem::operator==(rItem)); + + const ScTpDefaultsItem& rPItem = static_cast<const ScTpDefaultsItem&>(rItem); + return ( theOptions == rPItem.theOptions ); +} + +ScTpDefaultsItem* ScTpDefaultsItem::Clone( SfxItemPool * ) const +{ + return new ScTpDefaultsItem( *this ); +} + +constexpr OUStringLiteral CFGPATH_FORMULA = u"Office.Calc/Defaults"; + +#define SCDEFAULTSOPT_TAB_COUNT 0 +#define SCDEFAULTSOPT_TAB_PREFIX 1 +#define SCDEFAULTSOPT_JUMBO_SHEETS 2 + +Sequence<OUString> ScDefaultsCfg::GetPropertyNames() +{ + return {"Sheet/SheetCount", // SCDEFAULTSOPT_TAB_COUNT + "Sheet/SheetPrefix", // SCDEFAULTSOPT_TAB_PREFIX + "Sheet/JumboSheets"}; // SCDEFAULTSOPT_JUMBO_SHEETS + +} + +ScDefaultsCfg::ScDefaultsCfg() : + ConfigItem( CFGPATH_FORMULA ) +{ + OUString aPrefix; + + Sequence<OUString> aNames = GetPropertyNames(); + Sequence<Any> aValues = GetProperties(aNames); + const Any* pValues = aValues.getConstArray(); + OSL_ENSURE(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if(aValues.getLength() != aNames.getLength()) + return; + + sal_Int32 nIntVal = 0; + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + if(pValues[nProp].hasValue()) + { + switch (nProp) + { + case SCDEFAULTSOPT_TAB_COUNT: + if (pValues[nProp] >>= nIntVal) + SetInitTabCount( static_cast<SCTAB>(nIntVal) ); + break; + case SCDEFAULTSOPT_TAB_PREFIX: + if (pValues[nProp] >>= aPrefix) + SetInitTabPrefix(aPrefix); + break; + case SCDEFAULTSOPT_JUMBO_SHEETS: +#if HAVE_FEATURE_JUMBO_SHEETS + { + bool bValue; + if (pValues[nProp] >>= bValue) + SetInitJumboSheets(bValue); + } +#endif + break; + } + } + } +} + +void ScDefaultsCfg::ImplCommit() +{ + Sequence<OUString> aNames = GetPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + for (int nProp = 0; nProp < aNames.getLength(); ++nProp) + { + switch(nProp) + { + case SCDEFAULTSOPT_TAB_COUNT: + pValues[nProp] <<= static_cast<sal_Int32>(GetInitTabCount()); + break; + case SCDEFAULTSOPT_TAB_PREFIX: + pValues[nProp] <<= GetInitTabPrefix(); + break; + case SCDEFAULTSOPT_JUMBO_SHEETS: + pValues[nProp] <<= GetInitJumboSheets(); + break; + } + } + PutProperties(aNames, aValues); +} + +void ScDefaultsCfg::SetOptions( const ScDefaultsOptions& rNew ) +{ + *static_cast<ScDefaultsOptions*>(this) = rNew; + SetModified(); +} + +void ScDefaultsCfg::Notify( const css::uno::Sequence< OUString >& ) {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/detdata.cxx b/sc/source/core/tool/detdata.cxx new file mode 100644 index 0000000000..8af4e0bf0c --- /dev/null +++ b/sc/source/core/tool/detdata.cxx @@ -0,0 +1,87 @@ +/* -*- 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 <algorithm> +#include <detdata.hxx> +#include <refupdat.hxx> + +ScDetOpList::ScDetOpList(const ScDetOpList& rList) : + bHasAddError( false ), + aDetOpDataVector( rList.aDetOpDataVector ) +{ +} + +void ScDetOpList::DeleteOnTab( SCTAB nTab ) +{ + std::erase_if(aDetOpDataVector, + [&nTab](const ScDetOpData& rxDetOpData) { + return rxDetOpData.GetPos().Tab() == nTab; // look for operations on the deleted sheet + }); +} + +void ScDetOpList::UpdateReference( const ScDocument* pDoc, UpdateRefMode eUpdateRefMode, + const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz ) +{ + for (auto& rxDetOpData : aDetOpDataVector ) + { + ScAddress aPos = rxDetOpData.GetPos(); + SCCOL nCol1 = aPos.Col(); + SCROW nRow1 = aPos.Row(); + SCTAB nTab1 = aPos.Tab(); + SCCOL nCol2 = nCol1; + SCROW nRow2 = nRow1; + SCTAB nTab2 = nTab1; + + ScRefUpdateRes eRes = + ScRefUpdate::Update( pDoc, eUpdateRefMode, + rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(), + rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(), nDx, nDy, nDz, + nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + if ( eRes != UR_NOTHING ) + rxDetOpData.SetPos( ScAddress( nCol1, nRow1, nTab1 ) ); + } +} + +void ScDetOpList::Append( const ScDetOpData& rDetOpData ) +{ + if ( rDetOpData.GetOperation() == SCDETOP_ADDERROR ) + bHasAddError = true; + + aDetOpDataVector.push_back( rDetOpData ); +} + +bool ScDetOpList::operator==( const ScDetOpList& r ) const +{ + // for Ref-Undo + + size_t nCount = Count(); + bool bEqual = ( nCount == r.Count() ); + for (size_t i=0; i<nCount && bEqual; i++) // order has to be the same + if ( !(aDetOpDataVector[i] == r.aDetOpDataVector[i]) ) // entries are different ? + bEqual = false; + + return bEqual; +} + +const ScDetOpData& ScDetOpList::GetObject( size_t nPos ) const +{ + return aDetOpDataVector[nPos]; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/detfunc.cxx b/sc/source/core/tool/detfunc.cxx new file mode 100644 index 0000000000..b5dc71b92a --- /dev/null +++ b/sc/source/core/tool/detfunc.cxx @@ -0,0 +1,1653 @@ +/* -*- 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 <scitems.hxx> +#include <svtools/colorcfg.hxx> +#include <editeng/eeitem.hxx> +#include <formula/errorcodes.hxx> +#include <o3tl/unit_conversion.hxx> +#include <svx/sdshitm.hxx> +#include <svx/sdsxyitm.hxx> +#include <svx/sdtditm.hxx> +#include <svx/svditer.hxx> +#include <svx/svdocapt.hxx> +#include <svx/svdocirc.hxx> +#include <svx/svdopath.hxx> +#include <svx/svdorect.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdundo.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflclit.hxx> +#include <svx/xlnclit.hxx> +#include <svx/xlnedcit.hxx> +#include <svx/xlnedit.hxx> +#include <svx/xlnedwit.hxx> +#include <svx/xlnstcit.hxx> +#include <svx/xlnstit.hxx> +#include <svx/xlnstwit.hxx> +#include <svx/xlnwtit.hxx> +#include <svx/sdtagitm.hxx> +#include <svx/sxcecitm.hxx> +#include <svl/whiter.hxx> +#include <osl/diagnose.h> + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> + +#include <attrib.hxx> +#include <detfunc.hxx> +#include <document.hxx> +#include <dociter.hxx> +#include <drwlayer.hxx> +#include <userdat.hxx> +#include <validat.hxx> +#include <formulacell.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <undostyl.hxx> +#include <stlpool.hxx> +#include <docpool.hxx> +#include <patattr.hxx> +#include <scmod.hxx> +#include <postit.hxx> +#include <reftokenhelper.hxx> +#include <formulaiter.hxx> +#include <cellvalue.hxx> + +#include <vector> +#include <memory> + +using ::std::vector; +using namespace com::sun::star; + +namespace { + +enum DetInsertResult { // return-values for inserting in one level + DET_INS_CONTINUE, + DET_INS_INSERTED, + DET_INS_EMPTY, + DET_INS_CIRCULAR }; + +} + +class ScDetectiveData +{ +private: + SfxItemSet aBoxSet; + SfxItemSet aArrowSet; + SfxItemSet aToTabSet; + SfxItemSet aFromTabSet; + SfxItemSet aCircleSet; //TODO: individually ? + sal_uInt16 nMaxLevel; + +public: + explicit ScDetectiveData( SdrModel* pModel ); + + SfxItemSet& GetBoxSet() { return aBoxSet; } + SfxItemSet& GetArrowSet() { return aArrowSet; } + SfxItemSet& GetToTabSet() { return aToTabSet; } + SfxItemSet& GetFromTabSet() { return aFromTabSet; } + SfxItemSet& GetCircleSet() { return aCircleSet; } + + void SetMaxLevel( sal_uInt16 nVal ) { nMaxLevel = nVal; } + sal_uInt16 GetMaxLevel() const { return nMaxLevel; } +}; + +Color ScDetectiveFunc::nArrowColor = Color(0); +Color ScDetectiveFunc::nErrorColor = Color(0); +Color ScDetectiveFunc::nCommentColor = Color(0); +bool ScDetectiveFunc::bColorsInitialized = false; + +static bool lcl_HasThickLine( const SdrObject& rObj ) +{ + // thin lines get width 0 -> everything greater 0 is a thick line + + return rObj.GetMergedItem(XATTR_LINEWIDTH).GetValue() > 0; +} + +ScDetectiveData::ScDetectiveData( SdrModel* pModel ) : + aBoxSet( pModel->GetItemPool(), svl::Items<SDRATTR_START, SDRATTR_END> ), + aArrowSet( pModel->GetItemPool(), svl::Items<SDRATTR_START, SDRATTR_END> ), + aToTabSet( pModel->GetItemPool(), svl::Items<SDRATTR_START, SDRATTR_END> ), + aFromTabSet( pModel->GetItemPool(), svl::Items<SDRATTR_START, SDRATTR_END> ), + aCircleSet( pModel->GetItemPool(), svl::Items<SDRATTR_START, SDRATTR_END> ), + nMaxLevel(0) +{ + + aBoxSet.Put( XLineColorItem( OUString(), ScDetectiveFunc::GetArrowColor() ) ); + aBoxSet.Put( XFillStyleItem( drawing::FillStyle_NONE ) ); + + // create default line endings (like XLineEndList::Create) + // to be independent from the configured line endings + + basegfx::B2DPolygon aTriangle; + aTriangle.append(basegfx::B2DPoint(10.0, 0.0)); + aTriangle.append(basegfx::B2DPoint(0.0, 30.0)); + aTriangle.append(basegfx::B2DPoint(20.0, 30.0)); + aTriangle.setClosed(true); + + basegfx::B2DPolygon aSquare; + aSquare.append(basegfx::B2DPoint(0.0, 0.0)); + aSquare.append(basegfx::B2DPoint(10.0, 0.0)); + aSquare.append(basegfx::B2DPoint(10.0, 10.0)); + aSquare.append(basegfx::B2DPoint(0.0, 10.0)); + aSquare.setClosed(true); + + basegfx::B2DPolygon aCircle(basegfx::utils::createPolygonFromEllipse(basegfx::B2DPoint(0.0, 0.0), 100.0, 100.0)); + aCircle.setClosed(true); + + const OUString aName; + + aArrowSet.Put( XLineStartItem( aName, basegfx::B2DPolyPolygon(aCircle) ) ); + aArrowSet.Put( XLineStartWidthItem( 200 ) ); + aArrowSet.Put( XLineStartCenterItem( true ) ); + aArrowSet.Put( XLineEndItem( aName, basegfx::B2DPolyPolygon(aTriangle) ) ); + aArrowSet.Put( XLineEndWidthItem( 200 ) ); + aArrowSet.Put( XLineEndCenterItem( false ) ); + + aToTabSet.Put( XLineStartItem( aName, basegfx::B2DPolyPolygon(aCircle) ) ); + aToTabSet.Put( XLineStartWidthItem( 200 ) ); + aToTabSet.Put( XLineStartCenterItem( true ) ); + aToTabSet.Put( XLineEndItem( aName, basegfx::B2DPolyPolygon(aSquare) ) ); + aToTabSet.Put( XLineEndWidthItem( 300 ) ); + aToTabSet.Put( XLineEndCenterItem( false ) ); + + aFromTabSet.Put( XLineStartItem( aName, basegfx::B2DPolyPolygon(aSquare) ) ); + aFromTabSet.Put( XLineStartWidthItem( 300 ) ); + aFromTabSet.Put( XLineStartCenterItem( true ) ); + aFromTabSet.Put( XLineEndItem( aName, basegfx::B2DPolyPolygon(aTriangle) ) ); + aFromTabSet.Put( XLineEndWidthItem( 200 ) ); + aFromTabSet.Put( XLineEndCenterItem( false ) ); + + aCircleSet.Put( XLineColorItem( OUString(), ScDetectiveFunc::GetErrorColor() ) ); + aCircleSet.Put( XFillStyleItem( drawing::FillStyle_NONE ) ); + aCircleSet.Put( XLineWidthItem( 55 ) ); // 54 = 1 Pixel +} + +void ScDetectiveFunc::Modified() +{ + rDoc.SetStreamValid(nTab, false); +} + +static bool Intersect( SCCOL nStartCol1, SCROW nStartRow1, SCCOL nEndCol1, SCROW nEndRow1, + SCCOL nStartCol2, SCROW nStartRow2, SCCOL nEndCol2, SCROW nEndRow2 ) +{ + return nEndCol1 >= nStartCol2 && nEndCol2 >= nStartCol1 && + nEndRow1 >= nStartRow2 && nEndRow2 >= nStartRow1; +} + +bool ScDetectiveFunc::HasError( const ScRange& rRange, ScAddress& rErrPos ) +{ + rErrPos = rRange.aStart; + FormulaError nError = FormulaError::NONE; + + ScCellIterator aIter( rDoc, rRange); + for (bool bHasCell = aIter.first(); bHasCell; bHasCell = aIter.next()) + { + if (aIter.getType() != CELLTYPE_FORMULA) + continue; + + nError = aIter.getFormulaCell()->GetErrCode(); + if (nError != FormulaError::NONE) + rErrPos = aIter.GetPos(); + } + + return (nError != FormulaError::NONE); +} + +Point ScDetectiveFunc::GetDrawPos( SCCOL nCol, SCROW nRow, DrawPosMode eMode ) const +{ + OSL_ENSURE( rDoc.ValidColRow( nCol, nRow ), "ScDetectiveFunc::GetDrawPos - invalid cell address" ); + nCol = rDoc.SanitizeCol( nCol ); + nRow = rDoc.SanitizeRow( nRow ); + + Point aPos; + + switch( eMode ) + { + case DrawPosMode::TopLeft: + break; + case DrawPosMode::BottomRight: + ++nCol; + ++nRow; + break; + case DrawPosMode::DetectiveArrow: + aPos.AdjustX(rDoc.GetColWidth( nCol, nTab ) / 4 ); + aPos.AdjustY(rDoc.GetRowHeight( nRow, nTab ) / 2 ); + break; + } + + for ( SCCOL i = 0; i < nCol; ++i ) + aPos.AdjustX(rDoc.GetColWidth( i, nTab ) ); + aPos.AdjustY(rDoc.GetRowHeight( 0, nRow - 1, nTab ) ); + + aPos.setX(o3tl::convert(aPos.X(), o3tl::Length::twip, o3tl::Length::mm100)); + aPos.setY(o3tl::convert(aPos.Y(), o3tl::Length::twip, o3tl::Length::mm100)); + + if ( rDoc.IsNegativePage( nTab ) ) + aPos.setX( aPos.X() * -1 ); + + return aPos; +} + +tools::Rectangle ScDetectiveFunc::GetDrawRect( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) const +{ + tools::Rectangle aRect( + GetDrawPos( ::std::min( nCol1, nCol2 ), ::std::min( nRow1, nRow2 ), DrawPosMode::TopLeft ), + GetDrawPos( ::std::max( nCol1, nCol2 ), ::std::max( nRow1, nRow2 ), DrawPosMode::BottomRight ) ); + aRect.Normalize(); // reorder left/right in RTL sheets + return aRect; +} + +tools::Rectangle ScDetectiveFunc::GetDrawRect( SCCOL nCol, SCROW nRow ) const +{ + return GetDrawRect( nCol, nRow, nCol, nRow ); +} + +static bool lcl_IsOtherTab( const basegfx::B2DPolyPolygon& rPolyPolygon ) +{ + // test if rPolygon is the line end for "other table" (rectangle) + if(1 == rPolyPolygon.count()) + { + const basegfx::B2DPolygon& aSubPoly(rPolyPolygon.getB2DPolygon(0)); + + // #i73305# circle consists of 4 segments, too, distinguishable from square by + // the use of control points + if(4 == aSubPoly.count() && aSubPoly.isClosed() && !aSubPoly.areControlPointsUsed()) + { + return true; + } + } + + return false; +} + +bool ScDetectiveFunc::HasArrow( const ScAddress& rStart, + SCCOL nEndCol, SCROW nEndRow, SCTAB nEndTab ) +{ + bool bStartAlien = ( rStart.Tab() != nTab ); + bool bEndAlien = ( nEndTab != nTab ); + + if (bStartAlien && bEndAlien) + { + OSL_FAIL("bStartAlien && bEndAlien"); + return true; + } + + tools::Rectangle aStartRect; + tools::Rectangle aEndRect; + if (!bStartAlien) + aStartRect = GetDrawRect( rStart.Col(), rStart.Row() ); + if (!bEndAlien) + aEndRect = GetDrawRect( nEndCol, nEndRow ); + + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + bool bFound = false; + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject && !bFound) + { + if ( pObject->GetLayer()==SC_LAYER_INTERN && + pObject->IsPolyObj() && pObject->GetPointCount()==2 ) + { + const SfxItemSet& rSet = pObject->GetMergedItemSet(); + + bool bObjStartAlien = + lcl_IsOtherTab( rSet.Get(XATTR_LINESTART).GetLineStartValue() ); + bool bObjEndAlien = + lcl_IsOtherTab( rSet.Get(XATTR_LINEEND).GetLineEndValue() ); + + bool bStartHit = bStartAlien ? bObjStartAlien : + ( !bObjStartAlien && aStartRect.Contains(pObject->GetPoint(0)) ); + bool bEndHit = bEndAlien ? bObjEndAlien : + ( !bObjEndAlien && aEndRect.Contains(pObject->GetPoint(1)) ); + + if ( bStartHit && bEndHit ) + bFound = true; + } + pObject = aIter.Next(); + } + + return bFound; +} + +bool ScDetectiveFunc::IsNonAlienArrow( const SdrObject* pObject ) +{ + if ( pObject->GetLayer()==SC_LAYER_INTERN && + pObject->IsPolyObj() && pObject->GetPointCount()==2 ) + { + const SfxItemSet& rSet = pObject->GetMergedItemSet(); + + bool bObjStartAlien = + lcl_IsOtherTab( rSet.Get(XATTR_LINESTART).GetLineStartValue() ); + bool bObjEndAlien = + lcl_IsOtherTab( rSet.Get(XATTR_LINEEND).GetLineEndValue() ); + + return !bObjStartAlien && !bObjEndAlien; + } + + return false; +} + +// InsertXXX: called from DrawEntry/DrawAlienEntry and InsertObject + +void ScDetectiveFunc::InsertArrow( SCCOL nCol, SCROW nRow, + SCCOL nRefStartCol, SCROW nRefStartRow, + SCCOL nRefEndCol, SCROW nRefEndRow, + bool bFromOtherTab, bool bRed, + ScDetectiveData& rData ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + + bool bArea = ( nRefStartCol != nRefEndCol || nRefStartRow != nRefEndRow ); + if (bArea && !bFromOtherTab) + { + // insert the rectangle before the arrow - this is relied on in FindFrameForObject + + tools::Rectangle aRect = GetDrawRect( nRefStartCol, nRefStartRow, nRefEndCol, nRefEndRow ); + rtl::Reference<SdrRectObj> pBox = new SdrRectObj( + *pModel, + aRect); + + pBox->NbcSetStyleSheet(nullptr, true); + pBox->SetMergedItemSetAndBroadcast(rData.GetBoxSet()); + + pBox->SetLayer( SC_LAYER_INTERN ); + pPage->InsertObject( pBox.get() ); + pModel->AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pBox ) ); + + ScDrawObjData* pData = ScDrawLayer::GetObjData( pBox.get(), true ); + pData->maStart.Set( nRefStartCol, nRefStartRow, nTab); + pData->maEnd.Set( nRefEndCol, nRefEndRow, nTab); + } + + Point aStartPos = GetDrawPos( nRefStartCol, nRefStartRow, DrawPosMode::DetectiveArrow ); + Point aEndPos = GetDrawPos( nCol, nRow, DrawPosMode::DetectiveArrow ); + + if (bFromOtherTab) + { + bool bNegativePage = rDoc.IsNegativePage( nTab ); + tools::Long nPageSign = bNegativePage ? -1 : 1; + + aStartPos = Point( aEndPos.X() - 1000 * nPageSign, aEndPos.Y() - 1000 ); + if (aStartPos.X() * nPageSign < 0) + aStartPos.AdjustX(2000 * nPageSign ); + if (aStartPos.Y() < 0) + aStartPos.AdjustY(2000 ); + } + + SfxItemSet& rAttrSet = bFromOtherTab ? rData.GetFromTabSet() : rData.GetArrowSet(); + + if (bArea && !bFromOtherTab) + rAttrSet.Put( XLineWidthItem( 50 ) ); // range + else + rAttrSet.Put( XLineWidthItem( 0 ) ); // single reference + + Color nColor = ( bRed ? GetErrorColor() : GetArrowColor() ); + rAttrSet.Put( XLineColorItem( OUString(), nColor ) ); + + basegfx::B2DPolygon aTempPoly; + aTempPoly.append(basegfx::B2DPoint(aStartPos.X(), aStartPos.Y())); + aTempPoly.append(basegfx::B2DPoint(aEndPos.X(), aEndPos.Y())); + rtl::Reference<SdrPathObj> pArrow = new SdrPathObj( + *pModel, + SdrObjKind::Line, + basegfx::B2DPolyPolygon(aTempPoly)); + pArrow->NbcSetStyleSheet(nullptr, true); + pArrow->NbcSetLogicRect(tools::Rectangle::Normalize(aStartPos,aEndPos)); //TODO: needed ??? + pArrow->SetMergedItemSetAndBroadcast(rAttrSet); + + pArrow->SetLayer( SC_LAYER_INTERN ); + pPage->InsertObject( pArrow.get() ); + pModel->AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pArrow ) ); + + ScDrawObjData* pData = ScDrawLayer::GetObjData(pArrow.get(), true); + if (bFromOtherTab) + pData->maStart.SetInvalid(); + else + pData->maStart.Set( nRefStartCol, nRefStartRow, nTab); + + pData->maEnd.Set( nCol, nRow, nTab); + pData->meType = ScDrawObjData::DetectiveArrow; + + Modified(); +} + +void ScDetectiveFunc::InsertToOtherTab( SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, bool bRed, + ScDetectiveData& rData ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + + bool bArea = ( nStartCol != nEndCol || nStartRow != nEndRow ); + if (bArea) + { + tools::Rectangle aRect = GetDrawRect( nStartCol, nStartRow, nEndCol, nEndRow ); + rtl::Reference<SdrRectObj> pBox = new SdrRectObj( + *pModel, + aRect); + + pBox->NbcSetStyleSheet(nullptr, true); + pBox->SetMergedItemSetAndBroadcast(rData.GetBoxSet()); + + pBox->SetLayer( SC_LAYER_INTERN ); + pPage->InsertObject( pBox.get() ); + pModel->AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pBox ) ); + + ScDrawObjData* pData = ScDrawLayer::GetObjData( pBox.get(), true ); + pData->maStart.Set( nStartCol, nStartRow, nTab); + pData->maEnd.Set( nEndCol, nEndRow, nTab); + } + + bool bNegativePage = rDoc.IsNegativePage( nTab ); + tools::Long nPageSign = bNegativePage ? -1 : 1; + + Point aStartPos = GetDrawPos( nStartCol, nStartRow, DrawPosMode::DetectiveArrow ); + Point aEndPos( aStartPos.X() + 1000 * nPageSign, aStartPos.Y() - 1000 ); + if (aEndPos.Y() < 0) + aEndPos.AdjustY(2000 ); + + SfxItemSet& rAttrSet = rData.GetToTabSet(); + if (bArea) + rAttrSet.Put( XLineWidthItem( 50 ) ); // range + else + rAttrSet.Put( XLineWidthItem( 0 ) ); // single reference + + Color nColor = ( bRed ? GetErrorColor() : GetArrowColor() ); + rAttrSet.Put( XLineColorItem( OUString(), nColor ) ); + + basegfx::B2DPolygon aTempPoly; + aTempPoly.append(basegfx::B2DPoint(aStartPos.X(), aStartPos.Y())); + aTempPoly.append(basegfx::B2DPoint(aEndPos.X(), aEndPos.Y())); + rtl::Reference<SdrPathObj> pArrow = new SdrPathObj( + *pModel, + SdrObjKind::Line, + basegfx::B2DPolyPolygon(aTempPoly)); + pArrow->NbcSetStyleSheet(nullptr, true); + pArrow->NbcSetLogicRect(tools::Rectangle::Normalize(aStartPos,aEndPos)); //TODO: needed ??? + + pArrow->SetMergedItemSetAndBroadcast(rAttrSet); + + pArrow->SetLayer( SC_LAYER_INTERN ); + pPage->InsertObject( pArrow.get() ); + pModel->AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pArrow ) ); + + ScDrawObjData* pData = ScDrawLayer::GetObjData( pArrow.get(), true ); + pData->maStart.Set( nStartCol, nStartRow, nTab); + pData->maEnd.SetInvalid(); + + Modified(); +} + +// DrawEntry: formula from this spreadsheet, +// reference on this or other +// DrawAlienEntry: formula from other spreadsheet, +// reference on this + +// return FALSE: there was already an arrow + +bool ScDetectiveFunc::DrawEntry( SCCOL nCol, SCROW nRow, + const ScRange& rRef, + ScDetectiveData& rData ) +{ + if ( HasArrow( rRef.aStart, nCol, nRow, nTab ) ) + return false; + + ScAddress aErrorPos; + bool bError = HasError( rRef, aErrorPos ); + bool bAlien = ( rRef.aEnd.Tab() < nTab || rRef.aStart.Tab() > nTab ); + + InsertArrow( nCol, nRow, + rRef.aStart.Col(), rRef.aStart.Row(), + rRef.aEnd.Col(), rRef.aEnd.Row(), + bAlien, bError, rData ); + return true; +} + +bool ScDetectiveFunc::DrawAlienEntry( const ScRange& rRef, + ScDetectiveData& rData ) +{ + if ( HasArrow( rRef.aStart, 0, 0, nTab+1 ) ) + return false; + + ScAddress aErrorPos; + bool bError = HasError( rRef, aErrorPos ); + + InsertToOtherTab( rRef.aStart.Col(), rRef.aStart.Row(), + rRef.aEnd.Col(), rRef.aEnd.Row(), + bError, rData ); + return true; +} + +void ScDetectiveFunc::DrawCircle( SCCOL nCol, SCROW nRow, ScDetectiveData& rData ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + + tools::Rectangle aRect = ScDrawLayer::GetCellRect(rDoc, ScAddress(nCol, nRow, nTab), true); + aRect.AdjustLeft( -250 ); + aRect.AdjustRight(250 ); + aRect.AdjustTop( -70 ); + aRect.AdjustBottom(70 ); + + rtl::Reference<SdrCircObj> pCircle = new SdrCircObj( + *pModel, + SdrCircKind::Full, + aRect); + SfxItemSet& rAttrSet = rData.GetCircleSet(); + + pCircle->NbcSetStyleSheet(nullptr, true); + pCircle->SetMergedItemSetAndBroadcast(rAttrSet); + + pCircle->SetLayer( SC_LAYER_INTERN ); + pPage->InsertObject( pCircle.get() ); + pModel->AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pCircle ) ); + + ScDrawObjData* pData = ScDrawLayer::GetObjData( pCircle.get(), true ); + pData->maStart.Set( nCol, nRow, nTab); + pData->maEnd.SetInvalid(); + pData->meType = ScDrawObjData::ValidationCircle; + + Modified(); +} + +void ScDetectiveFunc::DeleteArrowsAt( SCCOL nCol, SCROW nRow, bool bDestPnt ) +{ + tools::Rectangle aRect = GetDrawRect( nCol, nRow ); + + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + pPage->RecalcObjOrdNums(); + + const size_t nObjCount = pPage->GetObjCount(); + if (!nObjCount) + return; + + size_t nDelCount = 0; + std::unique_ptr<SdrObject*[]> ppObj(new SdrObject*[nObjCount]); + + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetLayer()==SC_LAYER_INTERN && + pObject->IsPolyObj() && pObject->GetPointCount()==2 ) + { + if (aRect.Contains(pObject->GetPoint(bDestPnt ? 1 : 0))) // start/destinationpoint + ppObj[nDelCount++] = pObject; + } + + pObject = aIter.Next(); + } + + const bool bRecording = pModel->IsRecording(); + + if (bRecording) + { + for (size_t i=1; i<=nDelCount; ++i) + pModel->AddCalcUndo(std::make_unique<SdrUndoDelObj>(*ppObj[nDelCount-i])); + } + + for (size_t i=1; i<=nDelCount; ++i) + { + // remove the object from the drawing page, delete if undo is disabled + pPage->RemoveObject(ppObj[nDelCount-i]->GetOrdNum()); + } + + ppObj.reset(); + + Modified(); +} + + // delete box around reference + +#define SC_DET_TOLERANCE 50 + +static bool RectIsPoints( const tools::Rectangle& rRect, const Point& rStart, const Point& rEnd ) +{ + return rRect.Left() >= rStart.X() - SC_DET_TOLERANCE + && rRect.Left() <= rStart.X() + SC_DET_TOLERANCE + && rRect.Right() >= rEnd.X() - SC_DET_TOLERANCE + && rRect.Right() <= rEnd.X() + SC_DET_TOLERANCE + && rRect.Top() >= rStart.Y() - SC_DET_TOLERANCE + && rRect.Top() <= rStart.Y() + SC_DET_TOLERANCE + && rRect.Bottom() >= rEnd.Y() - SC_DET_TOLERANCE + && rRect.Bottom() <= rEnd.Y() + SC_DET_TOLERANCE; +} + +#undef SC_DET_TOLERANCE + +void ScDetectiveFunc::DeleteBox( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) +{ + tools::Rectangle aCornerRect = GetDrawRect( nCol1, nRow1, nCol2, nRow2 ); + Point aStartCorner = aCornerRect.TopLeft(); + Point aEndCorner = aCornerRect.BottomRight(); + tools::Rectangle aObjRect; + + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + pPage->RecalcObjOrdNums(); + + const size_t nObjCount = pPage->GetObjCount(); + if (!nObjCount) + return; + + size_t nDelCount = 0; + std::unique_ptr<SdrObject*[]> ppObj(new SdrObject*[nObjCount]); + + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetLayer() == SC_LAYER_INTERN ) + if ( auto pSdrRectObj = dynamic_cast< const SdrRectObj* >(pObject) ) + { + aObjRect = pSdrRectObj->GetLogicRect(); + aObjRect.Normalize(); + if ( RectIsPoints( aObjRect, aStartCorner, aEndCorner ) ) + ppObj[nDelCount++] = pObject; + } + + pObject = aIter.Next(); + } + + for (size_t i=1; i<=nDelCount; ++i) + pModel->AddCalcUndo( std::make_unique<SdrUndoRemoveObj>( *ppObj[nDelCount-i] ) ); + + for (size_t i=1; i<=nDelCount; ++i) + pPage->RemoveObject( ppObj[nDelCount-i]->GetOrdNum() ); + + ppObj.reset(); + + Modified(); +} + +sal_uInt16 ScDetectiveFunc::InsertPredLevelArea( const ScRange& rRef, + ScDetectiveData& rData, sal_uInt16 nLevel ) +{ + sal_uInt16 nResult = DET_INS_EMPTY; + + ScCellIterator aIter( rDoc, rRef); + for (bool bHasCell = aIter.first(); bHasCell; bHasCell = aIter.next()) + { + if (aIter.getType() != CELLTYPE_FORMULA) + continue; + + const ScAddress& rPos = aIter.GetPos(); + switch (InsertPredLevel(rPos.Col(), rPos.Row(), rData, nLevel)) + { + case DET_INS_INSERTED: + nResult = DET_INS_INSERTED; + break; + case DET_INS_CONTINUE: + if (nResult != DET_INS_INSERTED) + nResult = DET_INS_CONTINUE; + break; + case DET_INS_CIRCULAR: + if (nResult == DET_INS_EMPTY) + nResult = DET_INS_CIRCULAR; + break; + default: + ; + } + } + + return nResult; +} + +sal_uInt16 ScDetectiveFunc::InsertPredLevel( SCCOL nCol, SCROW nRow, ScDetectiveData& rData, + sal_uInt16 nLevel ) +{ + ScRefCellValue aCell(rDoc, ScAddress(nCol, nRow, nTab)); + if (aCell.getType() != CELLTYPE_FORMULA) + return DET_INS_EMPTY; + + ScFormulaCell* pFCell = aCell.getFormula(); + if (pFCell->IsRunning()) + return DET_INS_CIRCULAR; + + if (pFCell->GetDirty()) + pFCell->Interpret(); // can't be called after SetRunning + pFCell->SetRunning(true); + + sal_uInt16 nResult = DET_INS_EMPTY; + + ScDetectiveRefIter aIter(rDoc, pFCell); + ScRange aRef; + while ( aIter.GetNextRef( aRef ) ) + { + if (DrawEntry( nCol, nRow, aRef, rData )) + { + nResult = DET_INS_INSERTED; // insert new arrow + } + else + { + // continue + + if ( nLevel < rData.GetMaxLevel() ) + { + sal_uInt16 nSubResult; + bool bArea = (aRef.aStart != aRef.aEnd); + if (bArea) + nSubResult = InsertPredLevelArea( aRef, rData, nLevel+1 ); + else + nSubResult = InsertPredLevel( aRef.aStart.Col(), aRef.aStart.Row(), + rData, nLevel+1 ); + + switch (nSubResult) + { + case DET_INS_INSERTED: + nResult = DET_INS_INSERTED; + break; + case DET_INS_CONTINUE: + if (nResult != DET_INS_INSERTED) + nResult = DET_INS_CONTINUE; + break; + case DET_INS_CIRCULAR: + if (nResult == DET_INS_EMPTY) + nResult = DET_INS_CIRCULAR; + break; + // DET_INS_EMPTY: no change + } + } + else // nMaxLevel reached + if (nResult != DET_INS_INSERTED) + nResult = DET_INS_CONTINUE; + } + } + + pFCell->SetRunning(false); + + return nResult; +} + +sal_uInt16 ScDetectiveFunc::FindPredLevelArea( const ScRange& rRef, + sal_uInt16 nLevel, sal_uInt16 nDeleteLevel ) +{ + sal_uInt16 nResult = nLevel; + + ScCellIterator aCellIter( rDoc, rRef); + for (bool bHasCell = aCellIter.first(); bHasCell; bHasCell = aCellIter.next()) + { + if (aCellIter.getType() != CELLTYPE_FORMULA) + continue; + + sal_uInt16 nTemp = FindPredLevel(aCellIter.GetPos().Col(), aCellIter.GetPos().Row(), nLevel, nDeleteLevel); + if (nTemp > nResult) + nResult = nTemp; + } + + return nResult; +} + + // nDeleteLevel != 0 -> delete + +sal_uInt16 ScDetectiveFunc::FindPredLevel( SCCOL nCol, SCROW nRow, sal_uInt16 nLevel, sal_uInt16 nDeleteLevel ) +{ + OSL_ENSURE( nLevel<1000, "Level" ); + + ScRefCellValue aCell(rDoc, ScAddress(nCol, nRow, nTab)); + if (aCell.getType() != CELLTYPE_FORMULA) + return nLevel; + + ScFormulaCell* pFCell = aCell.getFormula(); + if (pFCell->IsRunning()) + return nLevel; + + if (pFCell->GetDirty()) + pFCell->Interpret(); // can't be called after SetRunning + pFCell->SetRunning(true); + + sal_uInt16 nResult = nLevel; + bool bDelete = ( nDeleteLevel && nLevel == nDeleteLevel-1 ); + + if ( bDelete ) + { + DeleteArrowsAt( nCol, nRow, true ); // arrows, that are pointing here + } + + ScDetectiveRefIter aIter(rDoc, pFCell); + ScRange aRef; + while ( aIter.GetNextRef( aRef) ) + { + bool bArea = ( aRef.aStart != aRef.aEnd ); + + if ( bDelete ) // delete frame ? + { + if (bArea) + { + DeleteBox( aRef.aStart.Col(), aRef.aStart.Row(), aRef.aEnd.Col(), aRef.aEnd.Row() ); + } + } + else // continue searching + { + if ( HasArrow( aRef.aStart, nCol,nRow,nTab ) ) + { + sal_uInt16 nTemp; + if (bArea) + nTemp = FindPredLevelArea( aRef, nLevel+1, nDeleteLevel ); + else + nTemp = FindPredLevel( aRef.aStart.Col(),aRef.aStart.Row(), + nLevel+1, nDeleteLevel ); + if (nTemp > nResult) + nResult = nTemp; + } + } + } + + pFCell->SetRunning(false); + + return nResult; +} + +sal_uInt16 ScDetectiveFunc::InsertErrorLevel( SCCOL nCol, SCROW nRow, ScDetectiveData& rData, + sal_uInt16 nLevel ) +{ + ScRefCellValue aCell(rDoc, ScAddress(nCol, nRow, nTab)); + if (aCell.getType() != CELLTYPE_FORMULA) + return DET_INS_EMPTY; + + ScFormulaCell* pFCell = aCell.getFormula(); + if (pFCell->IsRunning()) + return DET_INS_CIRCULAR; + + if (pFCell->GetDirty()) + pFCell->Interpret(); // can't be called after SetRunning + pFCell->SetRunning(true); + + sal_uInt16 nResult = DET_INS_EMPTY; + + ScDetectiveRefIter aIter(rDoc, pFCell); + ScRange aRef; + ScAddress aErrorPos; + bool bHasError = false; + while ( aIter.GetNextRef( aRef ) ) + { + if (HasError( aRef, aErrorPos )) + { + bHasError = true; + if (DrawEntry( nCol, nRow, ScRange( aErrorPos), rData )) + nResult = DET_INS_INSERTED; + + if ( nLevel < rData.GetMaxLevel() ) // hits most of the time + { + if (InsertErrorLevel( aErrorPos.Col(), aErrorPos.Row(), + rData, nLevel+1 ) == DET_INS_INSERTED) + nResult = DET_INS_INSERTED; + } + } + } + + pFCell->SetRunning(false); + + // leaves ? + if (!bHasError) + if (InsertPredLevel( nCol, nRow, rData, rData.GetMaxLevel() ) == DET_INS_INSERTED) + nResult = DET_INS_INSERTED; + + return nResult; +} + +sal_uInt16 ScDetectiveFunc::InsertSuccLevel( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + ScDetectiveData& rData, sal_uInt16 nLevel ) +{ + // over the entire document. + + sal_uInt16 nResult = DET_INS_EMPTY; + ScCellIterator aCellIter(rDoc, ScRange(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB)); // all sheets + for (bool bHas = aCellIter.first(); bHas; bHas = aCellIter.next()) + { + if (aCellIter.getType() != CELLTYPE_FORMULA) + continue; + + ScFormulaCell* pFCell = aCellIter.getFormulaCell(); + bool bRunning = pFCell->IsRunning(); + + if (pFCell->GetDirty()) + pFCell->Interpret(); // can't be called after SetRunning + pFCell->SetRunning(true); + + ScDetectiveRefIter aIter(rDoc, pFCell); + ScRange aRef; + while ( aIter.GetNextRef( aRef) ) + { + if (aRef.aStart.Tab() <= nTab && aRef.aEnd.Tab() >= nTab) + { + if (Intersect( nCol1,nRow1,nCol2,nRow2, + aRef.aStart.Col(),aRef.aStart.Row(), + aRef.aEnd.Col(),aRef.aEnd.Row() )) + { + bool bAlien = ( aCellIter.GetPos().Tab() != nTab ); + bool bDrawRet; + if (bAlien) + bDrawRet = DrawAlienEntry( aRef, rData ); + else + bDrawRet = DrawEntry( aCellIter.GetPos().Col(), aCellIter.GetPos().Row(), + aRef, rData ); + if (bDrawRet) + { + nResult = DET_INS_INSERTED; // insert new arrow + } + else + { + if (bRunning) + { + if (nResult == DET_INS_EMPTY) + nResult = DET_INS_CIRCULAR; + } + else + { + + if ( nLevel < rData.GetMaxLevel() ) + { + sal_uInt16 nSubResult = InsertSuccLevel( + aCellIter.GetPos().Col(), aCellIter.GetPos().Row(), + aCellIter.GetPos().Col(), aCellIter.GetPos().Row(), + rData, nLevel+1 ); + switch (nSubResult) + { + case DET_INS_INSERTED: + nResult = DET_INS_INSERTED; + break; + case DET_INS_CONTINUE: + if (nResult != DET_INS_INSERTED) + nResult = DET_INS_CONTINUE; + break; + case DET_INS_CIRCULAR: + if (nResult == DET_INS_EMPTY) + nResult = DET_INS_CIRCULAR; + break; + // DET_INS_EMPTY: leave unchanged + } + } + else // nMaxLevel reached + if (nResult != DET_INS_INSERTED) + nResult = DET_INS_CONTINUE; + } + } + } + } + } + pFCell->SetRunning(bRunning); + } + + return nResult; +} + +sal_uInt16 ScDetectiveFunc::FindSuccLevel( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + sal_uInt16 nLevel, sal_uInt16 nDeleteLevel ) +{ + OSL_ENSURE( nLevel<1000, "Level" ); + + sal_uInt16 nResult = nLevel; + bool bDelete = ( nDeleteLevel && nLevel == nDeleteLevel-1 ); + + ScCellIterator aCellIter( rDoc, ScRange(0, 0, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab) ); + for (bool bHas = aCellIter.first(); bHas; bHas = aCellIter.next()) + { + if (aCellIter.getType() != CELLTYPE_FORMULA) + continue; + + ScFormulaCell* pFCell = aCellIter.getFormulaCell(); + bool bRunning = pFCell->IsRunning(); + + if (pFCell->GetDirty()) + pFCell->Interpret(); // can't be called after SetRunning + pFCell->SetRunning(true); + + ScDetectiveRefIter aIter(rDoc, pFCell); + ScRange aRef; + while ( aIter.GetNextRef( aRef) ) + { + if (aRef.aStart.Tab() <= nTab && aRef.aEnd.Tab() >= nTab) + { + if (Intersect( nCol1,nRow1,nCol2,nRow2, + aRef.aStart.Col(),aRef.aStart.Row(), + aRef.aEnd.Col(),aRef.aEnd.Row() )) + { + if ( bDelete ) // arrows, that are starting here + { + if (aRef.aStart != aRef.aEnd) + { + DeleteBox( aRef.aStart.Col(), aRef.aStart.Row(), + aRef.aEnd.Col(), aRef.aEnd.Row() ); + } + DeleteArrowsAt( aRef.aStart.Col(), aRef.aStart.Row(), false ); + } + else if ( !bRunning && + HasArrow( aRef.aStart, + aCellIter.GetPos().Col(),aCellIter.GetPos().Row(),aCellIter.GetPos().Tab() ) ) + { + sal_uInt16 nTemp = FindSuccLevel( aCellIter.GetPos().Col(), aCellIter.GetPos().Row(), + aCellIter.GetPos().Col(), aCellIter.GetPos().Row(), + nLevel+1, nDeleteLevel ); + if (nTemp > nResult) + nResult = nTemp; + } + } + } + } + + pFCell->SetRunning(bRunning); + } + + return nResult; +} + +bool ScDetectiveFunc::ShowPred( SCCOL nCol, SCROW nRow ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return false; + + ScDetectiveData aData( pModel ); + + sal_uInt16 nMaxLevel = 0; + sal_uInt16 nResult = DET_INS_CONTINUE; + while (nResult == DET_INS_CONTINUE && nMaxLevel < 1000) + { + aData.SetMaxLevel( nMaxLevel ); + nResult = InsertPredLevel( nCol, nRow, aData, 0 ); + ++nMaxLevel; + } + + return ( nResult == DET_INS_INSERTED ); +} + +bool ScDetectiveFunc::ShowSucc( SCCOL nCol, SCROW nRow ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return false; + + ScDetectiveData aData( pModel ); + + sal_uInt16 nMaxLevel = 0; + sal_uInt16 nResult = DET_INS_CONTINUE; + while (nResult == DET_INS_CONTINUE && nMaxLevel < 1000) + { + aData.SetMaxLevel( nMaxLevel ); + nResult = InsertSuccLevel( nCol, nRow, nCol, nRow, aData, 0 ); + ++nMaxLevel; + } + + return ( nResult == DET_INS_INSERTED ); +} + +bool ScDetectiveFunc::ShowError( SCCOL nCol, SCROW nRow ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return false; + + ScRange aRange( nCol, nRow, nTab ); + ScAddress aErrPos; + if ( !HasError( aRange,aErrPos ) ) + return false; + + ScDetectiveData aData( pModel ); + + aData.SetMaxLevel( 1000 ); + sal_uInt16 nResult = InsertErrorLevel( nCol, nRow, aData, 0 ); + + return ( nResult == DET_INS_INSERTED ); +} + +bool ScDetectiveFunc::DeleteSucc( SCCOL nCol, SCROW nRow ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return false; + + sal_uInt16 nLevelCount = FindSuccLevel( nCol, nRow, nCol, nRow, 0, 0 ); + if ( nLevelCount ) + FindSuccLevel( nCol, nRow, nCol, nRow, 0, nLevelCount ); // delete + + return ( nLevelCount != 0 ); +} + +bool ScDetectiveFunc::DeletePred( SCCOL nCol, SCROW nRow ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return false; + + sal_uInt16 nLevelCount = FindPredLevel( nCol, nRow, 0, 0 ); + if ( nLevelCount ) + FindPredLevel( nCol, nRow, 0, nLevelCount ); // delete + + return ( nLevelCount != 0 ); +} + +bool ScDetectiveFunc::DeleteCirclesAt( SCCOL nCol, SCROW nRow ) +{ + tools::Rectangle aRect = ScDrawLayer::GetCellRect(rDoc, ScAddress(nCol, nRow, nTab), true); + aRect.AdjustLeft(-250); + aRect.AdjustRight(250); + aRect.AdjustTop(-70); + aRect.AdjustBottom(70); + + Point aStartCorner = aRect.TopLeft(); + Point aEndCorner = aRect.BottomRight(); + + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return false; + + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + OSL_ENSURE(pPage, "Page ?"); + + pPage->RecalcObjOrdNums(); + + const size_t nObjCount = pPage->GetObjCount(); + size_t nDelCount = 0; + if (nObjCount) + { + std::unique_ptr<SdrObject*[]> ppObj(new SdrObject*[nObjCount]); + + SdrObjListIter aIter(pPage, SdrIterMode::Flat); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if (pObject->GetLayer() == SC_LAYER_INTERN) + if (auto pSdrCircObj = dynamic_cast<const SdrCircObj*>(pObject) ) + { + tools::Rectangle aObjRect = pSdrCircObj->GetLogicRect(); + if (RectIsPoints(aObjRect, aStartCorner, aEndCorner)) + ppObj[nDelCount++] = pObject; + } + + pObject = aIter.Next(); + } + + for (size_t i = 1; i <= nDelCount; ++i) + pModel->AddCalcUndo(std::make_unique<SdrUndoRemoveObj>(*ppObj[nDelCount - i])); + + for (size_t i = 1; i <= nDelCount; ++i) + pPage->RemoveObject(ppObj[nDelCount - i]->GetOrdNum()); + + ppObj.reset(); + + Modified(); + } + + return (nDelCount != 0); +} + +bool ScDetectiveFunc::DeleteAll( ScDetectiveDelete eWhat ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return false; + + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + pPage->RecalcObjOrdNums(); + + size_t nDelCount = 0; + const size_t nObjCount = pPage->GetObjCount(); + if (nObjCount) + { + std::unique_ptr<SdrObject*[]> ppObj(new SdrObject*[nObjCount]); + + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetLayer() == SC_LAYER_INTERN ) + { + bool bDoThis = true; + bool bCircle = ( dynamic_cast<const SdrCircObj*>( pObject) != nullptr ); + bool bCaption = ScDrawLayer::IsNoteCaption( pObject ); + if ( eWhat == ScDetectiveDelete::Detective ) // detective, from menu + bDoThis = !bCaption; // also circles + else if ( eWhat == ScDetectiveDelete::Circles ) // circles, if new created + bDoThis = bCircle; + else if ( eWhat == ScDetectiveDelete::Arrows ) // DetectiveRefresh + bDoThis = !bCaption && !bCircle; // don't include circles + else + { + OSL_FAIL("what?"); + } + if ( bDoThis ) + ppObj[nDelCount++] = pObject; + } + + pObject = aIter.Next(); + } + + for (size_t i=1; i<=nDelCount; ++i) + pModel->AddCalcUndo( std::make_unique<SdrUndoRemoveObj>( *ppObj[nDelCount-i] ) ); + + for (size_t i=1; i<=nDelCount; ++i) + pPage->RemoveObject( ppObj[nDelCount-i]->GetOrdNum() ); + + ppObj.reset(); + + Modified(); + } + + return ( nDelCount != 0 ); +} + +bool ScDetectiveFunc::MarkInvalid(bool& rOverflow) +{ + rOverflow = false; + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return false; + + bool bDeleted = DeleteAll( ScDetectiveDelete::Circles ); // just circles + + ScDetectiveData aData( pModel ); + tools::Long nInsCount = 0; + + // search for valid places + ScDocAttrIterator aAttrIter( rDoc, nTab, 0,0,rDoc.MaxCol(),rDoc.MaxRow() ); + SCCOL nCol; + SCROW nRow1; + SCROW nRow2; + const ScPatternAttr* pPattern = aAttrIter.GetNext( nCol, nRow1, nRow2 ); + while ( pPattern && nInsCount < SC_DET_MAXCIRCLE ) + { + sal_uInt32 nIndex = pPattern->GetItem(ATTR_VALIDDATA).GetValue(); + if (nIndex) + { + const ScValidationData* pData = rDoc.GetValidationEntry( nIndex ); + if ( pData ) + { + // pass cells in this area + + bool bMarkEmpty = !pData->IsIgnoreBlank(); + SCROW nNextRow = nRow1; + SCROW nRow; + ScCellIterator aCellIter( rDoc, ScRange(nCol, nRow1, nTab, nCol, nRow2, nTab) ); + for (bool bHas = aCellIter.first(); bHas && nInsCount < SC_DET_MAXCIRCLE; bHas = aCellIter.next()) + { + SCROW nCellRow = aCellIter.GetPos().Row(); + if ( bMarkEmpty ) + for ( nRow = nNextRow; nRow < nCellRow && nInsCount < SC_DET_MAXCIRCLE; nRow++ ) + { + if(!pPattern->GetItem(ATTR_MERGE_FLAG).IsOverlapped()) + DrawCircle( nCol, nRow, aData ); + ++nInsCount; + } + ScRefCellValue aCell = aCellIter.getRefCellValue(); + if (!pData->IsDataValid(aCell, aCellIter.GetPos())) + { + if(!pPattern->GetItem(ATTR_MERGE_FLAG).IsOverlapped()) + DrawCircle( nCol, nCellRow, aData ); + ++nInsCount; + } + nNextRow = nCellRow + 1; + } + if ( bMarkEmpty ) + for ( nRow = nNextRow; nRow <= nRow2 && nInsCount < SC_DET_MAXCIRCLE; nRow++ ) + { + if(!pPattern->GetItem(ATTR_MERGE_FLAG).IsOverlapped()) + DrawCircle(nCol, nRow, aData); + ++nInsCount; + } + } + } + + pPattern = aAttrIter.GetNext( nCol, nRow1, nRow2 ); + } + + if ( nInsCount >= SC_DET_MAXCIRCLE ) + rOverflow = true; + + return ( bDeleted || nInsCount != 0 ); +} + +void ScDetectiveFunc::GetAllPreds(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + vector<ScTokenRef>& rRefTokens) +{ + ScCellIterator aIter(rDoc, ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab)); + for (bool bHas = aIter.first(); bHas; bHas = aIter.next()) + { + if (aIter.getType() != CELLTYPE_FORMULA) + continue; + + ScFormulaCell* pFCell = aIter.getFormulaCell(); + ScDetectiveRefIter aRefIter(rDoc, pFCell); + for (formula::FormulaToken* p = aRefIter.GetNextRefToken(); p; p = aRefIter.GetNextRefToken()) + { + ScTokenRef pRef(p->Clone()); + ScRefTokenHelper::join(&rDoc, rRefTokens, pRef, aIter.GetPos()); + } + } +} + +void ScDetectiveFunc::GetAllSuccs(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + vector<ScTokenRef>& rRefTokens) +{ + vector<ScTokenRef> aSrcRange; + aSrcRange.push_back( + ScRefTokenHelper::createRefToken(rDoc, ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab))); + + ScCellIterator aIter(rDoc, ScRange(0, 0, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab)); + for (bool bHas = aIter.first(); bHas; bHas = aIter.next()) + { + if (aIter.getType() != CELLTYPE_FORMULA) + continue; + + ScFormulaCell* pFCell = aIter.getFormulaCell(); + ScDetectiveRefIter aRefIter(rDoc, pFCell); + for (formula::FormulaToken* p = aRefIter.GetNextRefToken(); p; p = aRefIter.GetNextRefToken()) + { + const ScAddress& aPos = aIter.GetPos(); + ScTokenRef pRef(p->Clone()); + if (ScRefTokenHelper::intersects(&rDoc, aSrcRange, pRef, aPos)) + { + // This address is absolute. + pRef = ScRefTokenHelper::createRefToken(rDoc, aPos); + ScRefTokenHelper::join(&rDoc, rRefTokens, pRef, ScAddress()); + } + } + } +} + +void ScDetectiveFunc::UpdateAllComments( ScDocument& rDoc ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return; + + auto pStyleSheet = rDoc.GetStyleSheetPool()->Find(ScResId(STR_STYLENAME_NOTE), SfxStyleFamily::Frame); + if (!pStyleSheet) + return; + + ScStyleSaveData aOldData, aNewData; + aOldData.InitFromStyle(pStyleSheet); + + auto& rSet = pStyleSheet->GetItemSet(); + rSet.Put(XFillStyleItem(drawing::FillStyle_SOLID)); + rSet.Put(XFillColorItem(OUString(), ScDetectiveFunc::GetCommentColor())); + static_cast<SfxStyleSheet*>(pStyleSheet)->Broadcast(SfxHint(SfxHintId::DataChanged)); + + aNewData.InitFromStyle(pStyleSheet); + + ScDocShell* pDocSh = rDoc.GetDocumentShell(); + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoModifyStyle>(pDocSh, pStyleSheet->GetFamily(), aOldData, aNewData)); +} + +void ScDetectiveFunc::UpdateAllArrowColors() +{ + // no undo actions necessary + + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return; + + for( SCTAB nObjTab = 0, nTabCount = rDoc.GetTableCount(); nObjTab < nTabCount; ++nObjTab ) + { + SdrPage* pPage = pModel->GetPage( static_cast< sal_uInt16 >( nObjTab ) ); + OSL_ENSURE( pPage, "Page ?" ); + if( pPage ) + { + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + for( SdrObject* pObject = aIter.Next(); pObject; pObject = aIter.Next() ) + { + if ( pObject->GetLayer() == SC_LAYER_INTERN ) + { + bool bArrow = false; + bool bError = false; + + ScAddress aPos; + ScRange aSource; + bool bDummy; + ScDetectiveObjType eType = GetDetectiveObjectType( pObject, nObjTab, aPos, aSource, bDummy ); + if ( eType == SC_DETOBJ_ARROW || eType == SC_DETOBJ_TOOTHERTAB ) + { + // source is valid, determine error flag from source range + + ScAddress aErrPos; + if ( HasError( aSource, aErrPos ) ) + bError = true; + else + bArrow = true; + } + else if ( eType == SC_DETOBJ_FROMOTHERTAB ) + { + // source range is no longer known, take error flag from formula itself + // (this means, if the formula has an error, all references to other tables + // are marked red) + + ScAddress aErrPos; + if ( HasError( ScRange( aPos), aErrPos ) ) + bError = true; + else + bArrow = true; + } + else if ( eType == SC_DETOBJ_CIRCLE ) + { + // circles (error marks) are always red + + bError = true; + } + else if ( eType == SC_DETOBJ_NONE ) + { + // frame for area reference has no ObjType, always gets arrow color + + if ( dynamic_cast<const SdrRectObj*>( pObject) != nullptr && dynamic_cast<const SdrCaptionObj*>( pObject) == nullptr ) + { + bArrow = true; + } + } + + if ( bArrow || bError ) + { + Color nColor = ( bError ? GetErrorColor() : GetArrowColor() ); + pObject->SetMergedItem( XLineColorItem( OUString(), nColor ) ); + + // repaint only + pObject->ActionChanged(); + } + } + } + } + } +} + +void ScDetectiveFunc::FindFrameForObject( const SdrObject* pObject, ScRange& rRange ) +{ + // find the rectangle for an arrow (always the object directly before the arrow) + // rRange must be initialized to the source cell of the arrow (start of area) + + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) return; + + SdrPage* pPage = pModel->GetPage(static_cast<sal_uInt16>(nTab)); + OSL_ENSURE(pPage,"Page ?"); + if (!pPage) return; + + // test if the object is a direct page member + if( !(pObject && pObject->getSdrPageFromSdrObject() && (pObject->getSdrPageFromSdrObject() == pObject->getParentSdrObjListFromSdrObject()->getSdrPageFromSdrObjList())) ) + return; + + // Is there a previous object? + const size_t nOrdNum = pObject->GetOrdNum(); + + if(nOrdNum <= 0) + return; + + SdrObject* pPrevObj = pPage->GetObj(nOrdNum - 1); + + if ( pPrevObj && pPrevObj->GetLayer() == SC_LAYER_INTERN && dynamic_cast<const SdrRectObj*>( pPrevObj) != nullptr ) + { + ScDrawObjData* pPrevData = ScDrawLayer::GetObjDataTab( pPrevObj, rRange.aStart.Tab() ); + if ( pPrevData && pPrevData->maStart.IsValid() && pPrevData->maEnd.IsValid() && (pPrevData->maStart == rRange.aStart) ) + { + rRange.aEnd = pPrevData->maEnd; + return; + } + } +} + +ScDetectiveObjType ScDetectiveFunc::GetDetectiveObjectType( SdrObject* pObject, SCTAB nObjTab, + ScAddress& rPosition, ScRange& rSource, bool& rRedLine ) +{ + rRedLine = false; + ScDetectiveObjType eType = SC_DETOBJ_NONE; + + if ( pObject && pObject->GetLayer() == SC_LAYER_INTERN ) + { + if ( ScDrawObjData* pData = ScDrawLayer::GetObjDataTab( pObject, nObjTab ) ) + { + bool bValidStart = pData->maStart.IsValid(); + bool bValidEnd = pData->maEnd.IsValid(); + + if ( pObject->IsPolyObj() && pObject->GetPointCount() == 2 ) + { + // line object -> arrow + + if ( bValidStart ) + eType = bValidEnd ? SC_DETOBJ_ARROW : SC_DETOBJ_TOOTHERTAB; + else if ( bValidEnd ) + eType = SC_DETOBJ_FROMOTHERTAB; + + if ( bValidStart ) + rSource = pData->maStart; + if ( bValidEnd ) + rPosition = pData->maEnd; + + if ( bValidStart && lcl_HasThickLine( *pObject ) ) + { + // thick line -> look for frame before this object + + FindFrameForObject( pObject, rSource ); // modifies rSource + } + + Color nObjColor = pObject->GetMergedItem(XATTR_LINECOLOR).GetColorValue(); + if ( nObjColor == GetErrorColor() && nObjColor != GetArrowColor() ) + rRedLine = true; + } + else if (dynamic_cast<const SdrCircObj*>(pObject) != nullptr) + { + if (bValidStart) + { + // cell position is returned in rPosition + rPosition = pData->maStart; + eType = SC_DETOBJ_CIRCLE; + } + } + else if (dynamic_cast<const SdrRectObj*>(pObject) != nullptr) + { + if (bValidStart) + { + // cell position is returned in rPosition + rPosition = pData->maStart; + eType = SC_DETOBJ_RECTANGLE; + } + } + } + } + + return eType; +} + +void ScDetectiveFunc::InsertObject( ScDetectiveObjType eType, + const ScAddress& rPosition, const ScRange& rSource, + bool bRedLine ) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) return; + ScDetectiveData aData( pModel ); + + switch (eType) + { + case SC_DETOBJ_ARROW: + case SC_DETOBJ_FROMOTHERTAB: + InsertArrow( rPosition.Col(), rPosition.Row(), + rSource.aStart.Col(), rSource.aStart.Row(), + rSource.aEnd.Col(), rSource.aEnd.Row(), + (eType == SC_DETOBJ_FROMOTHERTAB), bRedLine, aData ); + break; + case SC_DETOBJ_TOOTHERTAB: + InsertToOtherTab( rSource.aStart.Col(), rSource.aStart.Row(), + rSource.aEnd.Col(), rSource.aEnd.Row(), + bRedLine, aData ); + break; + case SC_DETOBJ_CIRCLE: + DrawCircle( rPosition.Col(), rPosition.Row(), aData ); + break; + default: + { + // added to avoid warnings + } + } +} + +Color ScDetectiveFunc::GetArrowColor() +{ + if (!bColorsInitialized) + InitializeColors(); + return nArrowColor; +} + +Color ScDetectiveFunc::GetErrorColor() +{ + if (!bColorsInitialized) + InitializeColors(); + return nErrorColor; +} + +Color ScDetectiveFunc::GetCommentColor() +{ + if (!bColorsInitialized) + InitializeColors(); + return nCommentColor; +} + +void ScDetectiveFunc::InitializeColors() +{ + // may be called several times to update colors from configuration + + const svtools::ColorConfig& rColorCfg = SC_MOD()->GetColorConfig(); + nArrowColor = rColorCfg.GetColorValue(svtools::CALCDETECTIVE).nColor; + nErrorColor = rColorCfg.GetColorValue(svtools::CALCDETECTIVEERROR).nColor; + nCommentColor = rColorCfg.GetColorValue(svtools::CALCNOTESBACKGROUND).nColor; + + bColorsInitialized = true; +} + +bool ScDetectiveFunc::IsColorsInitialized() +{ + return bColorsInitialized; +} + +void ScDetectiveFunc::AppendChangTrackNoteSeparator(OUString &rDisplay) +{ + rDisplay += "\n--------\n"; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/docoptio.cxx b/sc/source/core/tool/docoptio.cxx new file mode 100644 index 0000000000..1f88b07c9b --- /dev/null +++ b/sc/source/core/tool/docoptio.cxx @@ -0,0 +1,371 @@ +/* -*- 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 <svl/numformat.hxx> +#include <osl/diagnose.h> + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> + +#include <docoptio.hxx> +#include <miscuno.hxx> +#include <global.hxx> + +using namespace utl; +using namespace com::sun::star::uno; + + +using sc::TwipsToEvenHMM; + +static sal_uInt16 lcl_GetDefaultTabDist() +{ + if ( ScOptionsUtil::IsMetricSystem() ) + return 709; // 1,25 cm + else + return 720; // 1/2" +} + +// ScDocOptions - document options + +ScDocOptions::ScDocOptions() +{ + ResetDocOptions(); +} + +void ScDocOptions::ResetDocOptions() +{ + bIsIgnoreCase = false; + bIsIter = false; + nIterCount = 100; + fIterEps = 1.0E-3; + nPrecStandardFormat = SvNumberFormatter::UNLIMITED_PRECISION; + nDay = 30; + nMonth = 12; + nYear = 1899; + nYear2000 = SvNumberFormatter::GetYear2000Default(); + nTabDistance = lcl_GetDefaultTabDist(); + bCalcAsShown = false; + bMatchWholeCell = true; + bDoAutoSpell = false; + bLookUpColRowNames = true; + bFormulaRegexEnabled= false; + bFormulaWildcardsEnabled= true; + eFormulaSearchType = utl::SearchParam::SearchType::Wildcard; + bWriteCalcConfig = true; +} + +void ScDocOptions::SetFormulaRegexEnabled( bool bVal ) +{ + if (bVal) + { + bFormulaRegexEnabled = true; + bFormulaWildcardsEnabled = false; + eFormulaSearchType = utl::SearchParam::SearchType::Regexp; + } + else if (!bFormulaRegexEnabled) + ; // nothing changes for setting false to false + else + { + bFormulaRegexEnabled = false; + eFormulaSearchType = utl::SearchParam::SearchType::Unknown; + } +} + +void ScDocOptions::SetFormulaWildcardsEnabled( bool bVal ) +{ + if (bVal) + { + bFormulaRegexEnabled = false; + bFormulaWildcardsEnabled = true; + eFormulaSearchType = utl::SearchParam::SearchType::Wildcard; + } + else if (!bFormulaWildcardsEnabled) + ; // nothing changes for setting false to false + else + { + bFormulaWildcardsEnabled = false; + eFormulaSearchType = utl::SearchParam::SearchType::Unknown; + } +} + +// ScTpCalcItem - data for the CalcOptions TabPage + +ScTpCalcItem::ScTpCalcItem( sal_uInt16 nWhichP, const ScDocOptions& rOpt ) + : SfxPoolItem ( nWhichP ), + theOptions ( rOpt ) +{ +} + +ScTpCalcItem::~ScTpCalcItem() +{ +} + +bool ScTpCalcItem::operator==( const SfxPoolItem& rItem ) const +{ + assert(SfxPoolItem::operator==(rItem)); + + const ScTpCalcItem& rPItem = static_cast<const ScTpCalcItem&>(rItem); + + return ( theOptions == rPItem.theOptions ); +} + +ScTpCalcItem* ScTpCalcItem::Clone( SfxItemPool * ) const +{ + return new ScTpCalcItem( *this ); +} + +// Config Item containing document options + +constexpr OUStringLiteral CFGPATH_CALC = u"Office.Calc/Calculate"; + +#define SCCALCOPT_ITER_ITER 0 +#define SCCALCOPT_ITER_STEPS 1 +#define SCCALCOPT_ITER_MINCHG 2 +#define SCCALCOPT_DATE_DAY 3 +#define SCCALCOPT_DATE_MONTH 4 +#define SCCALCOPT_DATE_YEAR 5 +#define SCCALCOPT_DECIMALS 6 +#define SCCALCOPT_CASESENSITIVE 7 +#define SCCALCOPT_PRECISION 8 +#define SCCALCOPT_SEARCHCRIT 9 +#define SCCALCOPT_FINDLABEL 10 +#define SCCALCOPT_REGEX 11 +#define SCCALCOPT_WILDCARDS 12 + +constexpr OUStringLiteral CFGPATH_DOCLAYOUT = u"Office.Calc/Layout/Other"; + +#define SCDOCLAYOUTOPT_TABSTOP 0 + +Sequence<OUString> ScDocCfg::GetCalcPropertyNames() +{ + return {"IterativeReference/Iteration", // SCCALCOPT_ITER_ITER + "IterativeReference/Steps", // SCCALCOPT_ITER_STEPS + "IterativeReference/MinimumChange", // SCCALCOPT_ITER_MINCHG + "Other/Date/DD", // SCCALCOPT_DATE_DAY + "Other/Date/MM", // SCCALCOPT_DATE_MONTH + "Other/Date/YY", // SCCALCOPT_DATE_YEAR + "Other/DecimalPlaces", // SCCALCOPT_DECIMALS + "Other/CaseSensitive", // SCCALCOPT_CASESENSITIVE + "Other/Precision", // SCCALCOPT_PRECISION + "Other/SearchCriteria", // SCCALCOPT_SEARCHCRIT + "Other/FindLabel", // SCCALCOPT_FINDLABEL + "Other/RegularExpressions", // SCCALCOPT_REGEX + "Other/Wildcards"}; // SCCALCOPT_WILDCARDS +} + +Sequence<OUString> ScDocCfg::GetLayoutPropertyNames() +{ + if (ScOptionsUtil::IsMetricSystem()) + return {"TabStop/Metric"}; // SCDOCLAYOUTOPT_TABSTOP + else + return {"TabStop/NonMetric"}; // SCDOCLAYOUTOPT_TABSTOP +} + +ScDocCfg::ScDocCfg() : + aCalcItem( CFGPATH_CALC ), + aLayoutItem(CFGPATH_DOCLAYOUT) +{ + sal_Int32 nIntVal = 0; + + Sequence<OUString> aNames; + Sequence<Any> aValues; + const Any* pValues = nullptr; + + sal_uInt16 nDateDay, nDateMonth; + sal_Int16 nDateYear; + GetDate( nDateDay, nDateMonth, nDateYear ); + + aNames = GetCalcPropertyNames(); + aValues = aCalcItem.GetProperties(aNames); + aCalcItem.EnableNotification(aNames); + pValues = aValues.getConstArray(); + OSL_ENSURE(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if(aValues.getLength() == aNames.getLength()) + { + double fDoubleVal = 0; + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + OSL_ENSURE(pValues[nProp].hasValue(), "property value missing"); + if(pValues[nProp].hasValue()) + { + switch(nProp) + { + case SCCALCOPT_ITER_ITER: + SetIter( ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCCALCOPT_ITER_STEPS: + if (pValues[nProp] >>= nIntVal) SetIterCount( static_cast<sal_uInt16>(nIntVal) ); + break; + case SCCALCOPT_ITER_MINCHG: + if (pValues[nProp] >>= fDoubleVal) SetIterEps( fDoubleVal ); + break; + case SCCALCOPT_DATE_DAY: + if (pValues[nProp] >>= nIntVal) nDateDay = static_cast<sal_uInt16>(nIntVal); + break; + case SCCALCOPT_DATE_MONTH: + if (pValues[nProp] >>= nIntVal) nDateMonth = static_cast<sal_uInt16>(nIntVal); + break; + case SCCALCOPT_DATE_YEAR: + if (pValues[nProp] >>= nIntVal) nDateYear = static_cast<sal_Int16>(nIntVal); + break; + case SCCALCOPT_DECIMALS: + if (pValues[nProp] >>= nIntVal) SetStdPrecision( static_cast<sal_uInt16>(nIntVal) ); + break; + case SCCALCOPT_CASESENSITIVE: + // content is reversed + SetIgnoreCase( !ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCCALCOPT_PRECISION: + SetCalcAsShown( ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCCALCOPT_SEARCHCRIT: + SetMatchWholeCell( ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCCALCOPT_FINDLABEL: + SetLookUpColRowNames( ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCCALCOPT_REGEX : + SetFormulaRegexEnabled( ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCCALCOPT_WILDCARDS : + SetFormulaWildcardsEnabled( ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + } + } + } + } + aCalcItem.SetCommitLink( LINK( this, ScDocCfg, CalcCommitHdl ) ); + + SetDate( nDateDay, nDateMonth, nDateYear ); + + aNames = GetLayoutPropertyNames(); + aValues = aLayoutItem.GetProperties(aNames); + aLayoutItem.EnableNotification(aNames); + pValues = aValues.getConstArray(); + OSL_ENSURE(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if(aValues.getLength() == aNames.getLength()) + { + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + OSL_ENSURE(pValues[nProp].hasValue(), "property value missing"); + if(pValues[nProp].hasValue()) + { + switch(nProp) + { + case SCDOCLAYOUTOPT_TABSTOP: + // TabDistance in ScDocOptions is in twips + if (pValues[nProp] >>= nIntVal) + SetTabDistance(o3tl::toTwips(nIntVal, o3tl::Length::mm100)); + break; + } + } + } + } + aLayoutItem.SetCommitLink( LINK( this, ScDocCfg, LayoutCommitHdl ) ); +} + +IMPL_LINK_NOARG(ScDocCfg, CalcCommitHdl, ScLinkConfigItem&, void) +{ + Sequence<OUString> aNames = GetCalcPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + sal_uInt16 nDateDay, nDateMonth; + sal_Int16 nDateYear; + GetDate( nDateDay, nDateMonth, nDateYear ); + + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + switch(nProp) + { + case SCCALCOPT_ITER_ITER: + pValues[nProp] <<= IsIter(); + break; + case SCCALCOPT_ITER_STEPS: + pValues[nProp] <<= static_cast<sal_Int32>(GetIterCount()); + break; + case SCCALCOPT_ITER_MINCHG: + pValues[nProp] <<= GetIterEps(); + break; + case SCCALCOPT_DATE_DAY: + pValues[nProp] <<= static_cast<sal_Int32>(nDateDay); + break; + case SCCALCOPT_DATE_MONTH: + pValues[nProp] <<= static_cast<sal_Int32>(nDateMonth); + break; + case SCCALCOPT_DATE_YEAR: + pValues[nProp] <<= static_cast<sal_Int32>(nDateYear); + break; + case SCCALCOPT_DECIMALS: + pValues[nProp] <<= static_cast<sal_Int32>(GetStdPrecision()); + break; + case SCCALCOPT_CASESENSITIVE: + // content is reversed + pValues[nProp] <<= !IsIgnoreCase(); + break; + case SCCALCOPT_PRECISION: + pValues[nProp] <<= IsCalcAsShown(); + break; + case SCCALCOPT_SEARCHCRIT: + pValues[nProp] <<= IsMatchWholeCell(); + break; + case SCCALCOPT_FINDLABEL: + pValues[nProp] <<= IsLookUpColRowNames(); + break; + case SCCALCOPT_REGEX : + pValues[nProp] <<= IsFormulaRegexEnabled(); + break; + case SCCALCOPT_WILDCARDS : + pValues[nProp] <<= IsFormulaWildcardsEnabled(); + break; + } + } + aCalcItem.PutProperties(aNames, aValues); +} + +IMPL_LINK_NOARG(ScDocCfg, LayoutCommitHdl, ScLinkConfigItem&, void) +{ + Sequence<OUString> aNames = GetLayoutPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + switch(nProp) + { + case SCDOCLAYOUTOPT_TABSTOP: + // TabDistance in ScDocOptions is in twips + // use only even numbers, so defaults don't get changed + // by modifying other settings in the same config item + pValues[nProp] <<= static_cast<sal_Int32>(TwipsToEvenHMM( GetTabDistance() )); + break; + } + } + aLayoutItem.PutProperties(aNames, aValues); +} + +void ScDocCfg::SetOptions( const ScDocOptions& rNew ) +{ + *static_cast<ScDocOptions*>(this) = rNew; + + aCalcItem.SetModified(); + aLayoutItem.SetModified(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/doubleref.cxx b/sc/source/core/tool/doubleref.cxx new file mode 100644 index 0000000000..66ecea4a10 --- /dev/null +++ b/sc/source/core/tool/doubleref.cxx @@ -0,0 +1,474 @@ +/* -*- 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 <doubleref.hxx> +#include <global.hxx> +#include <document.hxx> +#include <queryparam.hxx> +#include <queryentry.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <scmatrix.hxx> + +#include <svl/sharedstringpool.hxx> +#include <osl/diagnose.h> +#include <unotools/charclass.hxx> +#include <unotools/transliterationwrapper.hxx> + +#include <memory> +#include <utility> +#include <vector> + +using ::std::unique_ptr; +using ::std::vector; + +namespace { + +void lcl_uppercase(OUString& rStr) +{ + rStr = ScGlobal::getCharClass().uppercase(rStr.trim()); +} + +bool lcl_createStarQuery( + const ScDocument* pDoc, + svl::SharedStringPool& rPool, ScQueryParamBase* pParam, const ScDBRangeBase* pDBRef, const ScDBRangeBase* pQueryRef) +{ + // A valid StarQuery must be at least 4 columns wide. To be precise it + // should be exactly 4 columns ... + // Additionally, if this wasn't checked, a formula pointing to a valid 1-3 + // column Excel style query range immediately left to itself would result + // in a circular reference when the field name or operator or value (first + // to third query range column) is obtained (#i58354#). Furthermore, if the + // range wasn't sufficiently specified data changes wouldn't flag formula + // cells for recalculation. + + if (pQueryRef->getColSize() < 4) + return false; + + bool bValid; + OUString aCellStr; + SCSIZE nIndex = 0; + SCROW nRow = 0; + SCROW nRows = pDBRef->getRowSize(); + SCSIZE nNewEntries = static_cast<SCSIZE>(nRows); + pParam->Resize(nNewEntries); + + do + { + ScQueryEntry& rEntry = pParam->GetEntry(nIndex); + + bValid = false; + + if (nIndex > 0) + { + // For all entries after the first one, check the and/or connector in the first column. + aCellStr = pQueryRef->getString(0, nRow); + lcl_uppercase(aCellStr); + if ( aCellStr == ScResId(STR_TABLE_AND) ) + { + rEntry.eConnect = SC_AND; + bValid = true; + } + else if ( aCellStr == ScResId(STR_TABLE_OR) ) + { + rEntry.eConnect = SC_OR; + bValid = true; + } + } + + if ((nIndex < 1) || bValid) + { + // field name in the 2nd column. + aCellStr = pQueryRef->getString(1, nRow); + SCCOL nField = pDBRef->findFieldColumn(aCellStr); // TODO: must be case insensitive comparison. + if (pDoc->ValidCol(nField)) + { + rEntry.nField = nField; + bValid = true; + } + else + bValid = false; + } + + if (bValid) + { + // equality, non-equality operator in the 3rd column. + aCellStr = pQueryRef->getString(2, nRow); + lcl_uppercase(aCellStr); + const sal_Unicode* p = aCellStr.getStr(); + if (p[0] == '<') + { + if (p[1] == '>') + rEntry.eOp = SC_NOT_EQUAL; + else if (p[1] == '=') + rEntry.eOp = SC_LESS_EQUAL; + else + rEntry.eOp = SC_LESS; + } + else if (p[0] == '>') + { + if (p[1] == '=') + rEntry.eOp = SC_GREATER_EQUAL; + else + rEntry.eOp = SC_GREATER; + } + else if (p[0] == '=') + rEntry.eOp = SC_EQUAL; + + } + + if (bValid) + { + // Finally, the right-hand-side value in the 4th column. + rEntry.GetQueryItem().maString = + rPool.intern(pQueryRef->getString(3, nRow)); + rEntry.bDoQuery = true; + } + nIndex++; + nRow++; + } + while (bValid && (nRow < nRows) /* && (nIndex < MAXQUERY) */ ); + return bValid; +} + +bool lcl_createExcelQuery( + const ScDocument* pDoc, + svl::SharedStringPool& rPool, ScQueryParamBase* pParam, const ScDBRangeBase* pDBRef, const ScDBRangeBase* pQueryRef) +{ + bool bValid = true; + SCCOL nCols = pQueryRef->getColSize(); + SCROW nRows = pQueryRef->getRowSize(); + vector<SCCOL> aFields(nCols); + SCCOL nCol = 0; + while (bValid && (nCol < nCols)) + { + OUString aQueryStr = pQueryRef->getString(nCol, 0); + SCCOL nField = pDBRef->findFieldColumn(aQueryStr); + if (pDoc->ValidCol(nField)) + aFields[nCol] = nField; + else + bValid = false; + ++nCol; + } + + if (bValid) + { + // Count the number of visible cells (excluding the header row). Each + // visible cell corresponds with a single query. + SCSIZE nVisible = pQueryRef->getVisibleDataCellCount(); + if ( nVisible > SCSIZE_MAX / sizeof(void*) ) + { + OSL_FAIL("too many filter criteria"); + nVisible = 0; + } + + SCSIZE nNewEntries = nVisible; + pParam->Resize( nNewEntries ); + + SCSIZE nIndex = 0; + SCROW nRow = 1; + OUString aCellStr; + while (nRow < nRows) + { + nCol = 0; + while (nCol < nCols) + { + aCellStr = pQueryRef->getString(nCol, nRow); + aCellStr = ScGlobal::getCharClass().uppercase( aCellStr ); + if (!aCellStr.isEmpty()) + { + if (nIndex < nNewEntries) + { + pParam->GetEntry(nIndex).nField = aFields[nCol]; + pParam->FillInExcelSyntax(rPool, aCellStr, nIndex, nullptr); + nIndex++; + if (nIndex < nNewEntries) + pParam->GetEntry(nIndex).eConnect = SC_AND; + } + else + bValid = false; + } + nCol++; + } + nRow++; + if (nIndex < nNewEntries) + pParam->GetEntry(nIndex).eConnect = SC_OR; + } + } + return bValid; +} + +bool lcl_fillQueryEntries( + const ScDocument* pDoc, + svl::SharedStringPool& rPool, ScQueryParamBase* pParam, const ScDBRangeBase* pDBRef, const ScDBRangeBase* pQueryRef) +{ + SCSIZE nCount = pParam->GetEntryCount(); + for (SCSIZE i = 0; i < nCount; ++i) + pParam->GetEntry(i).Clear(); + + // Standard QueryTabelle + bool bValid = lcl_createStarQuery(pDoc, rPool, pParam, pDBRef, pQueryRef); + // Excel QueryTabelle + if (!bValid) + bValid = lcl_createExcelQuery(pDoc, rPool, pParam, pDBRef, pQueryRef); + + nCount = pParam->GetEntryCount(); + if (bValid) + { + // bQueryByString must be set + for (SCSIZE i = 0; i < nCount; ++i) + pParam->GetEntry(i).GetQueryItem().meType = ScQueryEntry::ByString; + } + else + { + // nothing + for (SCSIZE i = 0; i < nCount; ++i) + pParam->GetEntry(i).Clear(); + } + return bValid; +} + +} + +ScDBRangeBase::ScDBRangeBase(ScDocument* pDoc) : + mpDoc(pDoc) +{ +} + +ScDBRangeBase::~ScDBRangeBase() +{ +} + +bool ScDBRangeBase::fillQueryEntries(ScQueryParamBase* pParam, const ScDBRangeBase* pDBRef) const +{ + if (!pDBRef) + return false; + + return lcl_fillQueryEntries(getDoc(), getDoc()->GetSharedStringPool(), pParam, pDBRef, this); +} + +void ScDBRangeBase::fillQueryOptions(ScQueryParamBase* pParam) +{ + pParam->bHasHeader = true; + pParam->bByRow = true; + pParam->bInplace = true; + pParam->bCaseSens = false; + pParam->eSearchType = utl::SearchParam::SearchType::Normal; + pParam->bDuplicate = true; +} + +ScDBInternalRange::ScDBInternalRange(ScDocument* pDoc, const ScRange& rRange) : + ScDBRangeBase(pDoc), maRange(rRange) +{ +} + +ScDBInternalRange::~ScDBInternalRange() +{ +} + +SCCOL ScDBInternalRange::getColSize() const +{ + return maRange.aEnd.Col() - maRange.aStart.Col() + 1; +} + +SCROW ScDBInternalRange::getRowSize() const +{ + return maRange.aEnd.Row() - maRange.aStart.Row() + 1; +} + +SCSIZE ScDBInternalRange::getVisibleDataCellCount() const +{ + SCCOL nCols = getColSize(); + SCROW nRows = getRowSize(); + if (nRows <= 1) + return 0; + + return (nRows-1)*nCols; +} + +OUString ScDBInternalRange::getString(SCCOL nCol, SCROW nRow) const +{ + const ScAddress& s = maRange.aStart; + // #i109200# this is used in formula calculation, use GetInputString, not GetString + // (consistent with ScDBInternalRange::getCellString) + // GetStringForFormula is not used here, to allow querying for date values. + return getDoc()->GetInputString(s.Col() + nCol, s.Row() + nRow, maRange.aStart.Tab()); +} + +SCCOL ScDBInternalRange::getFirstFieldColumn() const +{ + return getRange().aStart.Col(); +} + +SCCOL ScDBInternalRange::findFieldColumn(SCCOL nIndex) const +{ + const ScRange& rRange = getRange(); + const ScAddress& s = rRange.aStart; + + SCCOL nDBCol1 = s.Col(); + + // Don't handle out-of-bound condition here. We'll do that later. + return nIndex + nDBCol1 - 1; +} + +SCCOL ScDBInternalRange::findFieldColumn(const OUString& rStr, FormulaError* pErr) const +{ + const ScAddress& s = maRange.aStart; + const ScAddress& e = maRange.aEnd; + OUString aUpper = rStr; + lcl_uppercase(aUpper); + + SCCOL nDBCol1 = s.Col(); + SCROW nDBRow1 = s.Row(); + SCTAB nDBTab1 = s.Tab(); + SCCOL nDBCol2 = e.Col(); + + bool bFound = false; + + OUString aCellStr; + ScAddress aLook( nDBCol1, nDBRow1, nDBTab1 ); + while (!bFound && (aLook.Col() <= nDBCol2)) + { + FormulaError nErr = getDoc()->GetStringForFormula( aLook, aCellStr ); + if (pErr) + *pErr = nErr; + lcl_uppercase(aCellStr); + bFound = ScGlobal::GetTransliteration().isEqual(aCellStr, aUpper); + if (!bFound) + aLook.IncCol(); + } + SCCOL nField = aLook.Col(); + + return bFound ? nField : -1; +} + +std::unique_ptr<ScDBQueryParamBase> ScDBInternalRange::createQueryParam(const ScDBRangeBase* pQueryRef) const +{ + unique_ptr<ScDBQueryParamInternal> pParam(new ScDBQueryParamInternal); + + // Set the database range first. + const ScAddress& s = maRange.aStart; + const ScAddress& e = maRange.aEnd; + pParam->nCol1 = s.Col(); + pParam->nRow1 = s.Row(); + pParam->nCol2 = e.Col(); + pParam->nRow2 = e.Row(); + pParam->nTab = s.Tab(); + + fillQueryOptions(pParam.get()); + + // Now construct the query entries from the query range. + if (!pQueryRef->fillQueryEntries(pParam.get(), this)) + return nullptr; + + return std::unique_ptr<ScDBQueryParamBase>(std::move(pParam)); +} + +bool ScDBInternalRange::isRangeEqual(const ScRange& rRange) const +{ + return maRange == rRange; +} + +ScDBExternalRange::ScDBExternalRange(ScDocument* pDoc, ScMatrixRef pMat) : + ScDBRangeBase(pDoc), mpMatrix(std::move(pMat)) +{ + SCSIZE nC, nR; + mpMatrix->GetDimensions(nC, nR); + mnCols = static_cast<SCCOL>(nC); + mnRows = static_cast<SCROW>(nR); +} + +ScDBExternalRange::~ScDBExternalRange() +{ +} + +SCCOL ScDBExternalRange::getColSize() const +{ + return mnCols; +} + +SCROW ScDBExternalRange::getRowSize() const +{ + return mnRows; +} + +SCSIZE ScDBExternalRange::getVisibleDataCellCount() const +{ + SCCOL nCols = getColSize(); + SCROW nRows = getRowSize(); + if (nRows <= 1) + return 0; + + return (nRows-1)*nCols; +} + +OUString ScDBExternalRange::getString(SCCOL nCol, SCROW nRow) const +{ + if (nCol >= mnCols || nRow >= mnRows) + return OUString(); + + return mpMatrix->GetString(nCol, nRow).getString(); +} + +SCCOL ScDBExternalRange::getFirstFieldColumn() const +{ + return 0; +} + +SCCOL ScDBExternalRange::findFieldColumn(SCCOL nIndex) const +{ + return nIndex - 1; +} + +SCCOL ScDBExternalRange::findFieldColumn(const OUString& rStr, FormulaError* pErr) const +{ + if (pErr) + *pErr = FormulaError::NONE; + + OUString aUpper = rStr; + lcl_uppercase(aUpper); + for (SCCOL i = 0; i < mnCols; ++i) + { + OUString aUpperVal = mpMatrix->GetString(i, 0).getString(); + lcl_uppercase(aUpperVal); + if (aUpper == aUpperVal) + return i; + } + return -1; +} + +std::unique_ptr<ScDBQueryParamBase> ScDBExternalRange::createQueryParam(const ScDBRangeBase* pQueryRef) const +{ + unique_ptr<ScDBQueryParamMatrix> pParam(new ScDBQueryParamMatrix); + pParam->mpMatrix = mpMatrix; + fillQueryOptions(pParam.get()); + + // Now construct the query entries from the query range. + if (!pQueryRef->fillQueryEntries(pParam.get(), this)) + return nullptr; + + return std::unique_ptr<ScDBQueryParamBase>(std::move(pParam)); +} + +bool ScDBExternalRange::isRangeEqual(const ScRange& /*rRange*/) const +{ + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/editdataarray.cxx b/sc/source/core/tool/editdataarray.cxx new file mode 100644 index 0000000000..00a2b6c71d --- /dev/null +++ b/sc/source/core/tool/editdataarray.cxx @@ -0,0 +1,75 @@ +/* -*- 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 <editdataarray.hxx> + +ScEditDataArray::ScEditDataArray() +{ +} + +ScEditDataArray::~ScEditDataArray() +{ +} + +void ScEditDataArray::AddItem(SCTAB nTab, SCCOL nCol, SCROW nRow, + std::unique_ptr<EditTextObject> pOldData, std::unique_ptr<EditTextObject> pNewData) +{ + maArray.emplace_back(nTab, nCol, nRow, std::move(pOldData), std::move(pNewData)); +} + +const ScEditDataArray::Item* ScEditDataArray::First() +{ + maIter = maArray.begin(); + if (maIter == maArray.end()) + return nullptr; + return &(*maIter++); +} + +const ScEditDataArray::Item* ScEditDataArray::Next() +{ + if (maIter == maArray.end()) + return nullptr; + return &(*maIter++); +} + +ScEditDataArray::Item::Item(SCTAB nTab, SCCOL nCol, SCROW nRow, + std::unique_ptr<EditTextObject> pOldData, std::unique_ptr<EditTextObject> pNewData) : + mpOldData(std::move(pOldData)), + mpNewData(std::move(pNewData)), + mnTab(nTab), + mnCol(nCol), + mnRow(nRow) +{ +} + +ScEditDataArray::Item::~Item() +{ +} + +const EditTextObject* ScEditDataArray::Item::GetOldData() const +{ + return mpOldData.get(); +} + +const EditTextObject* ScEditDataArray::Item::GetNewData() const +{ + return mpNewData.get(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/editutil.cxx b/sc/source/core/tool/editutil.cxx new file mode 100644 index 0000000000..57e93d5138 --- /dev/null +++ b/sc/source/core/tool/editutil.cxx @@ -0,0 +1,941 @@ +/* -*- 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 <scitems.hxx> +#include <comphelper/processfactory.hxx> +#include <editeng/eeitem.hxx> + +#include <svx/algitem.hxx> +#include <svtools/colorcfg.hxx> +#include <editeng/editstat.hxx> +#include <editeng/flditem.hxx> +#include <editeng/numitem.hxx> +#include <editeng/justifyitem.hxx> +#include <editeng/editobj.hxx> +#include <vcl/outdev.hxx> +#include <svl/numformat.hxx> +#include <svl/inethist.hxx> +#include <sfx2/objsh.hxx> +#include <comphelper/lok.hxx> +#include <osl/diagnose.h> + +#include <com/sun/star/text/textfield/Type.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> + +#include <editutil.hxx> +#include <global.hxx> +#include <attrib.hxx> +#include <document.hxx> +#include <docpool.hxx> +#include <docsh.hxx> +#include <patattr.hxx> +#include <scmod.hxx> +#include <inputopt.hxx> +#include <compiler.hxx> +#include <mutex> + +using namespace com::sun::star; + +// delimiters additionally to EditEngine default: + +ScEditUtil::ScEditUtil( ScDocument* pDocument, SCCOL nX, SCROW nY, SCTAB nZ, + const Point& rCellPos, + OutputDevice* pDevice, double nScaleX, double nScaleY, + const Fraction& rX, const Fraction& rY, bool bPrintTwips ) : + pDoc(pDocument),nCol(nX),nRow(nY),nTab(nZ), + aCellPos(rCellPos),pDev(pDevice), + nPPTX(nScaleX),nPPTY(nScaleY),aZoomX(rX),aZoomY(rY), + bInPrintTwips(bPrintTwips) {} + +OUString ScEditUtil::ModifyDelimiters( const OUString& rOld ) +{ + // underscore is used in function argument names + OUString aRet = rOld.replaceAll("_", "") + + "=()+-*/^&<>" + + ScCompiler::GetNativeSymbol(ocSep); // argument separator is localized. + return aRet; +} + +static OUString lcl_GetDelimitedString( const EditEngine& rEngine, const char c ) +{ + sal_Int32 nParCount = rEngine.GetParagraphCount(); + // avoid creating a new string if possible + if (nParCount == 0) + return OUString(); + else if (nParCount == 1) + return rEngine.GetText(0); + OUStringBuffer aRet( nParCount * 80 ); + for (sal_Int32 nPar=0; nPar<nParCount; nPar++) + { + if (nPar > 0) + aRet.append(c); + aRet.append( rEngine.GetText( nPar )); + } + return aRet.makeStringAndClear(); +} + +static OUString lcl_GetDelimitedString( const EditTextObject& rEdit, const char c ) +{ + sal_Int32 nParCount = rEdit.GetParagraphCount(); + OUStringBuffer aRet( nParCount * 80 ); + for (sal_Int32 nPar=0; nPar<nParCount; nPar++) + { + if (nPar > 0) + aRet.append(c); + aRet.append( rEdit.GetText( nPar )); + } + return aRet.makeStringAndClear(); +} + +OUString ScEditUtil::GetSpaceDelimitedString( const EditEngine& rEngine ) +{ + return lcl_GetDelimitedString(rEngine, ' '); +} +OUString ScEditUtil::GetMultilineString( const EditEngine& rEngine ) +{ + return lcl_GetDelimitedString(rEngine, '\n'); +} + +OUString ScEditUtil::GetMultilineString( const EditTextObject& rEdit ) +{ + return lcl_GetDelimitedString(rEdit, '\n'); +} + +OUString ScEditUtil::GetString( const EditTextObject& rEditText, const ScDocument* pDoc ) +{ + if( !rEditText.HasField()) + return GetMultilineString( rEditText ); + + static std::mutex aMutex; + std::scoped_lock aGuard( aMutex); + // ScFieldEditEngine is needed to resolve field contents. + if (pDoc) + { + /* TODO: make ScDocument::GetEditEngine() const? Most likely it's only + * not const because of the pointer assignment, make that mutable, and + * then remove the ugly const_cast here. */ + EditEngine& rEE = const_cast<ScDocument*>(pDoc)->GetEditEngine(); + rEE.SetText( rEditText); + return GetMultilineString( rEE); + } + else + { + EditEngine& rEE = ScGlobal::GetStaticFieldEditEngine(); + rEE.SetText( rEditText); + return GetMultilineString( rEE); + } +} + +std::unique_ptr<EditTextObject> ScEditUtil::CreateURLObjectFromURL( ScDocument& rDoc, const OUString& rURL, const OUString& rText ) +{ + SvxURLField aUrlField( rURL, rText, SvxURLFormat::AppDefault); + EditEngine& rEE = rDoc.GetEditEngine(); + rEE.SetText( OUString() ); + rEE.QuickInsertField( SvxFieldItem( aUrlField, EE_FEATURE_FIELD ), + ESelection( EE_PARA_MAX_COUNT, EE_TEXTPOS_MAX_COUNT ) ); + + return rEE.CreateTextObject(); +} + +void ScEditUtil::RemoveCharAttribs( EditTextObject& rEditText, const ScPatternAttr& rAttr ) +{ + static const struct { + sal_uInt16 nAttrType; + sal_uInt16 nCharType; + } AttrTypeMap[] = { + { ATTR_FONT, EE_CHAR_FONTINFO }, + { ATTR_CJK_FONT, EE_CHAR_FONTINFO_CJK }, + { ATTR_CTL_FONT, EE_CHAR_FONTINFO_CTL }, + { ATTR_FONT_HEIGHT, EE_CHAR_FONTHEIGHT }, + { ATTR_CJK_FONT_HEIGHT, EE_CHAR_FONTHEIGHT_CJK }, + { ATTR_CTL_FONT_HEIGHT, EE_CHAR_FONTHEIGHT_CTL }, + { ATTR_FONT_WEIGHT, EE_CHAR_WEIGHT }, + { ATTR_CJK_FONT_WEIGHT, EE_CHAR_WEIGHT_CJK }, + { ATTR_CTL_FONT_WEIGHT, EE_CHAR_WEIGHT_CTL }, + { ATTR_FONT_POSTURE, EE_CHAR_ITALIC }, + { ATTR_CJK_FONT_POSTURE, EE_CHAR_ITALIC_CJK }, + { ATTR_CTL_FONT_POSTURE, EE_CHAR_ITALIC_CTL }, + { ATTR_FONT_COLOR, EE_CHAR_COLOR }, + { ATTR_FONT_UNDERLINE, EE_CHAR_UNDERLINE }, + { ATTR_FONT_CROSSEDOUT, EE_CHAR_STRIKEOUT }, + { ATTR_FONT_CONTOUR, EE_CHAR_OUTLINE }, + { ATTR_FONT_SHADOWED, EE_CHAR_SHADOW } + }; + + const SfxItemSet& rSet = rAttr.GetItemSet(); + const SfxPoolItem* pItem; + for (size_t i = 0; i < SAL_N_ELEMENTS(AttrTypeMap); ++i) + { + if ( rSet.GetItemState(AttrTypeMap[i].nAttrType, false, &pItem) == SfxItemState::SET ) + rEditText.RemoveCharAttribs(AttrTypeMap[i].nCharType); + } +} + +std::unique_ptr<EditTextObject> ScEditUtil::Clone( const EditTextObject& rObj, ScDocument& rDestDoc ) +{ + std::unique_ptr<EditTextObject> pNew; + + EditEngine& rEngine = rDestDoc.GetEditEngine(); + if (rObj.HasOnlineSpellErrors()) + { + EEControlBits nControl = rEngine.GetControlWord(); + const EEControlBits nSpellControl = EEControlBits::ONLINESPELLING | EEControlBits::ALLOWBIGOBJS; + bool bNewControl = ( (nControl & nSpellControl) != nSpellControl ); + if (bNewControl) + rEngine.SetControlWord(nControl | nSpellControl); + rEngine.SetText(rObj); + pNew = rEngine.CreateTextObject(); + if (bNewControl) + rEngine.SetControlWord(nControl); + } + else + { + rEngine.SetText(rObj); + pNew = rEngine.CreateTextObject(); + } + + return pNew; +} + +OUString ScEditUtil::GetCellFieldValue( + const SvxFieldData& rFieldData, const ScDocument* pDoc, std::optional<Color>* ppTextColor, std::optional<FontLineStyle>* ppFldLineStyle ) +{ + OUString aRet; + switch (rFieldData.GetClassId()) + { + case text::textfield::Type::URL: + { + const SvxURLField& rField = static_cast<const SvxURLField&>(rFieldData); + const OUString& aURL = rField.GetURL(); + + switch (rField.GetFormat()) + { + case SvxURLFormat::AppDefault: //TODO: configurable with App??? + case SvxURLFormat::Repr: + aRet = rField.GetRepresentation(); + break; + case SvxURLFormat::Url: + aRet = aURL; + break; + default: + ; + } + + svtools::ColorConfigEntry eEntry = + INetURLHistory::GetOrCreate()->QueryUrl(aURL) ? svtools::LINKSVISITED : svtools::LINKS; + + if (ppTextColor) + *ppTextColor = SC_MOD()->GetColorConfig().GetColorValue(eEntry).nColor; + + if (ppFldLineStyle) + *ppFldLineStyle = FontLineStyle::LINESTYLE_SINGLE; + } + break; + case text::textfield::Type::EXTENDED_TIME: + { + const SvxExtTimeField& rField = static_cast<const SvxExtTimeField&>(rFieldData); + if (pDoc) + aRet = rField.GetFormatted(*pDoc->GetFormatTable(), ScGlobal::eLnge); + else + { + /* TODO: quite expensive, we could have a global formatter? */ + SvNumberFormatter aFormatter( comphelper::getProcessComponentContext(), ScGlobal::eLnge ); + aRet = rField.GetFormatted(aFormatter, ScGlobal::eLnge); + } + } + break; + case text::textfield::Type::DATE: + { + Date aDate(Date::SYSTEM); + aRet = ScGlobal::getLocaleData().getDate(aDate); + } + break; + case text::textfield::Type::DOCINFO_TITLE: + { + if (pDoc) + { + ScDocShell* pDocShell = pDoc->GetDocumentShell(); + if (pDocShell) + { + aRet = pDocShell->getDocProperties()->getTitle(); + if (aRet.isEmpty()) + aRet = pDocShell->GetTitle(); + } + } + if (aRet.isEmpty()) + aRet = "?"; + } + break; + case text::textfield::Type::TABLE: + { + const SvxTableField& rField = static_cast<const SvxTableField&>(rFieldData); + SCTAB nTab = rField.GetTab(); + OUString aName; + if (pDoc && pDoc->GetName(nTab, aName)) + aRet = aName; + else + aRet = "?"; + } + break; + default: + aRet = "?"; + } + + if (aRet.isEmpty()) // empty is yuck + aRet = " "; // space is default of EditEngine + + return aRet; +} + +tools::Long ScEditUtil::GetIndent(const ScPatternAttr* pPattern) const +{ + if (!pPattern) + pPattern = pDoc->GetPattern( nCol, nRow, nTab ); + + if ( pPattern->GetItem(ATTR_HOR_JUSTIFY).GetValue() == + SvxCellHorJustify::Left ) + { + tools::Long nIndent = pPattern->GetItem(ATTR_INDENT).GetValue(); + if (!bInPrintTwips) + nIndent = static_cast<tools::Long>(nIndent * nPPTX); + return nIndent; + } + + return 0; +} + +void ScEditUtil::GetMargins(const ScPatternAttr* pPattern, tools::Long& nLeftMargin, tools::Long& nTopMargin, + tools::Long& nRightMargin, tools::Long& nBottomMargin) const +{ + if (!pPattern) + pPattern = pDoc->GetPattern( nCol, nRow, nTab ); + + const SvxMarginItem* pMargin = &pPattern->GetItem(ATTR_MARGIN); + if (!pMargin) + return; + + nLeftMargin = bInPrintTwips ? pMargin->GetLeftMargin() : static_cast<tools::Long>(pMargin->GetLeftMargin() * nPPTX); + nRightMargin = bInPrintTwips ? pMargin->GetRightMargin() : static_cast<tools::Long>(pMargin->GetRightMargin() * nPPTX); + nTopMargin = bInPrintTwips ? pMargin->GetTopMargin() : static_cast<tools::Long>(pMargin->GetTopMargin() * nPPTY); + nBottomMargin = bInPrintTwips ? pMargin->GetBottomMargin() : static_cast<tools::Long>(pMargin->GetBottomMargin() * nPPTY); +} + +tools::Rectangle ScEditUtil::GetEditArea( const ScPatternAttr* pPattern, bool bForceToTop ) +{ + // bForceToTop = always align to top, for editing + // (sal_False for querying URLs etc.) + + if (!pPattern) + pPattern = pDoc->GetPattern( nCol, nRow, nTab ); + + Point aStartPos = aCellPos; + bool bIsTiledRendering = comphelper::LibreOfficeKit::isActive(); + + bool bLayoutRTL = pDoc->IsLayoutRTL( nTab ); + tools::Long nLayoutSign = (bLayoutRTL && !bIsTiledRendering) ? -1 : 1; + + const ScMergeAttr* pMerge = &pPattern->GetItem(ATTR_MERGE); + tools::Long nCellX = pDoc->GetColWidth(nCol,nTab); + if (!bInPrintTwips) + nCellX = static_cast<tools::Long>( nCellX * nPPTX ); + if ( pMerge->GetColMerge() > 1 ) + { + SCCOL nCountX = pMerge->GetColMerge(); + for (SCCOL i=1; i<nCountX; i++) + { + tools::Long nColWidth = pDoc->GetColWidth(nCol+i,nTab); + nCellX += (bInPrintTwips ? nColWidth : static_cast<tools::Long>( nColWidth * nPPTX )); + } + } + tools::Long nCellY = pDoc->GetRowHeight(nRow,nTab); + if (!bInPrintTwips) + nCellY = static_cast<tools::Long>( nCellY * nPPTY ); + if ( pMerge->GetRowMerge() > 1 ) + { + SCROW nCountY = pMerge->GetRowMerge(); + if (bInPrintTwips) + nCellY += pDoc->GetRowHeight(nRow + 1, nRow + nCountY - 1, nTab); + else + nCellY += pDoc->GetScaledRowHeight( nRow+1, nRow+nCountY-1, nTab, nPPTY); + } + + tools::Long nRightMargin = 0; + tools::Long nTopMargin = 0; + tools::Long nBottomMargin = 0; + tools::Long nDifX = 0; + { + tools::Long nLeftMargin = 0; + bool bInPrintTwipsOrig = bInPrintTwips; + bInPrintTwips = true; + tools::Long nIndent = GetIndent(pPattern); + GetMargins(pPattern, nLeftMargin, nTopMargin, nRightMargin, nBottomMargin); + bInPrintTwips = bInPrintTwipsOrig; + // Here rounding may be done only on the sum, ie nDifX, + // so need to get margin and indent in twips. + nDifX = nLeftMargin + nIndent; + if (!bInPrintTwips) + { + nDifX = static_cast<tools::Long>(nDifX * nPPTX); + nRightMargin = static_cast<tools::Long>(nRightMargin * nPPTX); + nTopMargin = static_cast<tools::Long>(nTopMargin * nPPTY); + nBottomMargin = static_cast<tools::Long>(nBottomMargin * nPPTY); + } + } + + + aStartPos.AdjustX(nDifX * nLayoutSign ); + nCellX -= nDifX + nRightMargin; // due to line feed, etc. + + // align vertical position to the one in the table + + tools::Long nDifY; + SvxCellVerJustify eJust = pPattern->GetItem(ATTR_VER_JUSTIFY).GetValue(); + + // asian vertical is always edited top-aligned + bool bAsianVertical = pPattern->GetItem( ATTR_STACKED ).GetValue() && + pPattern->GetItem( ATTR_VERTICAL_ASIAN ).GetValue(); + + if ( eJust == SvxCellVerJustify::Top || + ( bForceToTop && ( SC_MOD()->GetInputOptions().GetTextWysiwyg() || bAsianVertical ) ) ) + nDifY = nTopMargin; + else + { + MapMode aMode = pDev->GetMapMode(); + pDev->SetMapMode(MapMode(bInPrintTwips ? MapUnit::MapTwip : MapUnit::MapPixel)); + + tools::Long nTextHeight = pDoc->GetNeededSize( nCol, nRow, nTab, + pDev, nPPTX, nPPTY, aZoomX, aZoomY, false /* bWidth */, + false /* bTotalSize */, bInPrintTwips ); + if (!nTextHeight) + { // empty cell + vcl::Font aFont; + // font color doesn't matter here + pPattern->fillFontOnly(aFont, pDev, &aZoomY ); + pDev->SetFont(aFont); + nTextHeight = pDev->GetTextHeight() + nTopMargin + nBottomMargin; + } + + pDev->SetMapMode(aMode); + + if ( nTextHeight > nCellY + nTopMargin || bForceToTop ) + nDifY = 0; // too large -> begin at the top + else + { + if ( eJust == SvxCellVerJustify::Center ) + nDifY = nTopMargin + ( nCellY - nTextHeight ) / 2; + else + nDifY = nCellY - nTextHeight + nTopMargin; // JUSTIFY_BOTTOM + } + } + + aStartPos.AdjustY(nDifY ); + nCellY -= nDifY; + + if ( bLayoutRTL && !bIsTiledRendering ) + aStartPos.AdjustX( -(nCellX - 2) ); // excluding grid on both sides + + // -1 -> don't overwrite grid + return tools::Rectangle( aStartPos, Size(nCellX-1,nCellY-1) ); +} + +ScEditAttrTester::ScEditAttrTester( ScEditEngineDefaulter* pEngine ) : + bNeedsObject( false ), + bNeedsCellAttr( false ) +{ + if ( pEngine->GetParagraphCount() > 1 ) + { + bNeedsObject = true; //TODO: find cell attributes ? + } + else + { + const SfxPoolItem* pItem = nullptr; + pEditAttrs.reset( new SfxItemSet( pEngine->GetAttribs( + ESelection(0,0,0,pEngine->GetTextLen(0)), EditEngineAttribs::OnlyHard ) ) ); + const SfxItemSet& rEditDefaults = pEngine->GetDefaults(); + + for (sal_uInt16 nId = EE_CHAR_START; nId <= EE_CHAR_END && !bNeedsObject; nId++) + { + SfxItemState eState = pEditAttrs->GetItemState( nId, false, &pItem ); + if (eState == SfxItemState::DONTCARE) + bNeedsObject = true; + else if (eState == SfxItemState::SET) + { + if ( nId == EE_CHAR_ESCAPEMENT || nId == EE_CHAR_PAIRKERNING || + nId == EE_CHAR_KERNING || nId == EE_CHAR_XMLATTRIBS ) + { + // Escapement and kerning are kept in EditEngine because there are no + // corresponding cell format items. User defined attributes are kept in + // EditEngine because "user attributes applied to all the text" is different + // from "user attributes applied to the cell". + + if ( *pItem != rEditDefaults.Get(nId) ) + bNeedsObject = true; + } + else + if (!bNeedsCellAttr) + if ( *pItem != rEditDefaults.Get(nId) ) + bNeedsCellAttr = true; + // rEditDefaults contains the defaults from the cell format + } + } + + // contains field commands? + + SfxItemState eFieldState = pEditAttrs->GetItemState( EE_FEATURE_FIELD, false ); + if ( eFieldState == SfxItemState::DONTCARE || eFieldState == SfxItemState::SET ) + bNeedsObject = true; + + // not converted characters? + + SfxItemState eConvState = pEditAttrs->GetItemState( EE_FEATURE_NOTCONV, false ); + if ( eConvState == SfxItemState::DONTCARE || eConvState == SfxItemState::SET ) + bNeedsObject = true; + } +} + +ScEditAttrTester::~ScEditAttrTester() +{ +} + +ScEnginePoolHelper::ScEnginePoolHelper( SfxItemPool* pEnginePoolP, + bool bDeleteEnginePoolP ) + : + pEnginePool( pEnginePoolP ), + pDefaults( nullptr ), + bDeleteEnginePool( bDeleteEnginePoolP ), + bDeleteDefaults( false ) +{ +} + +ScEnginePoolHelper::ScEnginePoolHelper( const ScEnginePoolHelper& rOrg ) + : + pEnginePool( rOrg.bDeleteEnginePool ? rOrg.pEnginePool->Clone() : rOrg.pEnginePool ), + pDefaults( nullptr ), + bDeleteEnginePool( rOrg.bDeleteEnginePool ), + bDeleteDefaults( false ) +{ +} + +ScEnginePoolHelper::~ScEnginePoolHelper() +{ + if ( bDeleteDefaults ) + delete pDefaults; +} + +ScEditEngineDefaulter::ScEditEngineDefaulter( SfxItemPool* pEnginePoolP, + bool bDeleteEnginePoolP ) + : + ScEnginePoolHelper( pEnginePoolP, bDeleteEnginePoolP ), + EditEngine( pEnginePoolP ) +{ + // All EditEngines use ScGlobal::GetEditDefaultLanguage as DefaultLanguage. + // DefaultLanguage for InputHandler's EditEngine is updated later. + + SetDefaultLanguage( ScGlobal::GetEditDefaultLanguage() ); +} + +ScEditEngineDefaulter::ScEditEngineDefaulter( const ScEditEngineDefaulter& rOrg ) + : + ScEnginePoolHelper( rOrg ), + EditEngine( pEnginePool.get() ) +{ + SetDefaultLanguage( ScGlobal::GetEditDefaultLanguage() ); +} + +ScEditEngineDefaulter::~ScEditEngineDefaulter() +{ +} + +void ScEditEngineDefaulter::SetDefaults( const SfxItemSet& rSet, bool bRememberCopy ) +{ + if ( bRememberCopy ) + { + if ( bDeleteDefaults ) + delete pDefaults; + pDefaults = new SfxItemSet( rSet ); + bDeleteDefaults = true; + } + const SfxItemSet& rNewSet = bRememberCopy ? *pDefaults : rSet; + bool bUndo = IsUndoEnabled(); + EnableUndo( false ); + bool bUpdateMode = SetUpdateLayout( false ); + sal_Int32 nPara = GetParagraphCount(); + for ( sal_Int32 j=0; j<nPara; j++ ) + { + SetParaAttribs( j, rNewSet ); + } + if ( bUpdateMode ) + SetUpdateLayout( true ); + if ( bUndo ) + EnableUndo( true ); +} + +void ScEditEngineDefaulter::SetDefaults( std::unique_ptr<SfxItemSet> pSet ) +{ + if ( bDeleteDefaults ) + delete pDefaults; + pDefaults = pSet.release(); + bDeleteDefaults = true; + if ( pDefaults ) + SetDefaults( *pDefaults, false ); +} + +void ScEditEngineDefaulter::SetDefaultItem( const SfxPoolItem& rItem ) +{ + if ( !pDefaults ) + { + pDefaults = new SfxItemSet( GetEmptyItemSet() ); + bDeleteDefaults = true; + } + pDefaults->Put( rItem ); + SetDefaults( *pDefaults, false ); +} + +const SfxItemSet& ScEditEngineDefaulter::GetDefaults() +{ + if ( !pDefaults ) + { + pDefaults = new SfxItemSet( GetEmptyItemSet() ); + bDeleteDefaults = true; + } + return *pDefaults; +} + +void ScEditEngineDefaulter::SetTextCurrentDefaults( const EditTextObject& rTextObject ) +{ + bool bUpdateMode = SetUpdateLayout( false ); + SetText( rTextObject ); + if ( pDefaults ) + SetDefaults( *pDefaults, false ); + if ( bUpdateMode ) + SetUpdateLayout( true ); +} + +void ScEditEngineDefaulter::SetTextNewDefaults( const EditTextObject& rTextObject, + const SfxItemSet& rSet, bool bRememberCopy ) +{ + bool bUpdateMode = SetUpdateLayout( false ); + SetText( rTextObject ); + SetDefaults( rSet, bRememberCopy ); + if ( bUpdateMode ) + SetUpdateLayout( true ); +} + +void ScEditEngineDefaulter::SetTextCurrentDefaults( const OUString& rText ) +{ + bool bUpdateMode = SetUpdateLayout( false ); + SetText( rText ); + if ( pDefaults ) + SetDefaults( *pDefaults, false ); + if ( bUpdateMode ) + SetUpdateLayout( true ); +} + +void ScEditEngineDefaulter::SetTextNewDefaults( const OUString& rText, + const SfxItemSet& rSet ) +{ + bool bUpdateMode = SetUpdateLayout( false ); + SetText( rText ); + SetDefaults( rSet ); + if ( bUpdateMode ) + SetUpdateLayout( true ); +} + +void ScEditEngineDefaulter::RepeatDefaults() +{ + if ( pDefaults ) + { + sal_Int32 nPara = GetParagraphCount(); + for ( sal_Int32 j=0; j<nPara; j++ ) + SetParaAttribs( j, *pDefaults ); + } +} + +void ScEditEngineDefaulter::RemoveParaAttribs() +{ + std::optional<SfxItemSet> pCharItems; + bool bUpdateMode = SetUpdateLayout( false ); + sal_Int32 nParCount = GetParagraphCount(); + for (sal_Int32 nPar=0; nPar<nParCount; nPar++) + { + const SfxItemSet& rParaAttribs = GetParaAttribs( nPar ); + sal_uInt16 nWhich; + for (nWhich = EE_CHAR_START; nWhich <= EE_CHAR_END; nWhich ++) + { + const SfxPoolItem* pParaItem; + if ( rParaAttribs.GetItemState( nWhich, false, &pParaItem ) == SfxItemState::SET ) + { + // if defaults are set, use only items that are different from default + if ( !pDefaults || *pParaItem != pDefaults->Get(nWhich) ) + { + if (!pCharItems) + pCharItems.emplace( GetEmptyItemSet() ); + pCharItems->Put( *pParaItem ); + } + } + } + + if ( pCharItems ) + { + std::vector<sal_Int32> aPortions; + GetPortions( nPar, aPortions ); + + // loop through the portions of the paragraph, and set only those items + // that are not overridden by existing character attributes + + sal_Int32 nStart = 0; + for ( const sal_Int32 nEnd : aPortions ) + { + ESelection aSel( nPar, nStart, nPar, nEnd ); + SfxItemSet aOldCharAttrs = GetAttribs( aSel ); + SfxItemSet aNewCharAttrs = *pCharItems; + for (nWhich = EE_CHAR_START; nWhich <= EE_CHAR_END; nWhich ++) + { + // Clear those items that are different from existing character attributes. + // Where no character attributes are set, GetAttribs returns the paragraph attributes. + const SfxPoolItem* pItem; + if ( aNewCharAttrs.GetItemState( nWhich, false, &pItem ) == SfxItemState::SET && + *pItem != aOldCharAttrs.Get(nWhich) ) + { + aNewCharAttrs.ClearItem(nWhich); + } + } + if ( aNewCharAttrs.Count() ) + QuickSetAttribs( aNewCharAttrs, aSel ); + + nStart = nEnd; + } + + pCharItems.reset(); + } + + if ( rParaAttribs.Count() ) + { + // clear all paragraph attributes (including defaults), + // so they are not contained in resulting EditTextObjects + + SetParaAttribs( nPar, SfxItemSet( *rParaAttribs.GetPool(), rParaAttribs.GetRanges() ) ); + } + } + if ( bUpdateMode ) + SetUpdateLayout( true ); +} + +ScTabEditEngine::ScTabEditEngine( ScDocument* pDoc ) + : ScFieldEditEngine( pDoc, pDoc->GetEnginePool() ) +{ + SetEditTextObjectPool( pDoc->GetEditPool() ); + Init(pDoc->GetPool()->GetDefaultItem(ATTR_PATTERN)); +} + +ScTabEditEngine::ScTabEditEngine( const ScPatternAttr& rPattern, + SfxItemPool* pEngineItemPool, ScDocument* pDoc, SfxItemPool* pTextObjectPool ) + : ScFieldEditEngine( pDoc, pEngineItemPool, pTextObjectPool ) +{ + if ( pTextObjectPool ) + SetEditTextObjectPool( pTextObjectPool ); + Init( rPattern ); +} + +void ScTabEditEngine::Init( const ScPatternAttr& rPattern ) +{ + SetRefMapMode(MapMode(MapUnit::Map100thMM)); + auto pEditDefaults = std::make_unique<SfxItemSet>( GetEmptyItemSet() ); + rPattern.FillEditItemSet( pEditDefaults.get() ); + SetDefaults( std::move(pEditDefaults) ); + // we have no StyleSheets for text + SetControlWord( GetControlWord() & ~EEControlBits::RTFSTYLESHEETS ); +} + +// field commands for header and footer + +// numbers from \sw\source\core\doc\numbers.cxx + +static OUString lcl_GetCharStr( sal_Int32 nNo ) +{ + OSL_ENSURE( nNo, "0 is an invalid number !!" ); + OUString aStr; + + const sal_Int32 coDiff = 'Z' - 'A' +1; + sal_Int32 nCalc; + + do { + nCalc = nNo % coDiff; + if( !nCalc ) + nCalc = coDiff; + aStr = OUStringChar( sal_Unicode('a' - 1 + nCalc) ) + aStr; + nNo = sal::static_int_cast<sal_Int32>( nNo - nCalc ); + if( nNo ) + nNo /= coDiff; + } while( nNo ); + return aStr; +} + +static OUString lcl_GetNumStr(sal_Int32 nNo, SvxNumType eType) +{ + OUString aTmpStr('0'); + if( nNo ) + { + switch( eType ) + { + case css::style::NumberingType::CHARS_UPPER_LETTER: + case css::style::NumberingType::CHARS_LOWER_LETTER: + aTmpStr = lcl_GetCharStr( nNo ); + break; + + case css::style::NumberingType::ROMAN_UPPER: + case css::style::NumberingType::ROMAN_LOWER: + if( nNo < 4000 ) + aTmpStr = SvxNumberFormat::CreateRomanString( nNo, ( eType == css::style::NumberingType::ROMAN_UPPER ) ); + else + aTmpStr.clear(); + break; + + case css::style::NumberingType::NUMBER_NONE: + aTmpStr.clear(); + break; + +// CHAR_SPECIAL: +// ???? + +// case ARABIC: is default now + default: + aTmpStr = OUString::number(nNo); + break; + } + + if( css::style::NumberingType::CHARS_UPPER_LETTER == eType ) + aTmpStr = aTmpStr.toAsciiUpperCase(); + } + return aTmpStr; +} + +ScHeaderFieldData::ScHeaderFieldData() + : aDateTime ( DateTime::EMPTY ) +{ + nPageNo = nTotalPages = 0; + eNumType = SVX_NUM_ARABIC; +} + +ScHeaderEditEngine::ScHeaderEditEngine( SfxItemPool* pEnginePoolP ) + : ScEditEngineDefaulter( pEnginePoolP,true/*bDeleteEnginePoolP*/ ) +{ +} + +OUString ScHeaderEditEngine::CalcFieldValue( const SvxFieldItem& rField, + sal_Int32 /* nPara */, sal_Int32 /* nPos */, + std::optional<Color>& /* rTxtColor */, std::optional<Color>& /* rFldColor */, + std::optional<FontLineStyle>& /*rFldLineStyle*/ ) +{ + const SvxFieldData* pFieldData = rField.GetField(); + if (!pFieldData) + return "?"; + + OUString aRet; + sal_Int32 nClsId = pFieldData->GetClassId(); + switch (nClsId) + { + case text::textfield::Type::PAGE: + aRet = lcl_GetNumStr( aData.nPageNo,aData.eNumType ); + break; + case text::textfield::Type::PAGES: + aRet = lcl_GetNumStr( aData.nTotalPages,aData.eNumType ); + break; + case text::textfield::Type::EXTENDED_TIME: + case text::textfield::Type::TIME: + // For now, time field in the header / footer is always dynamic. + aRet = ScGlobal::getLocaleData().getTime(aData.aDateTime); + break; + case text::textfield::Type::DOCINFO_TITLE: + aRet = aData.aTitle; + break; + case text::textfield::Type::EXTENDED_FILE: + { + switch (static_cast<const SvxExtFileField*>(pFieldData)->GetFormat()) + { + case SvxFileFormat::PathFull : + aRet = aData.aLongDocName; + break; + default: + aRet = aData.aShortDocName; + } + } + break; + case text::textfield::Type::TABLE: + aRet = aData.aTabName; + break; + case text::textfield::Type::DATE: + aRet = ScGlobal::getLocaleData().getDate(aData.aDateTime); + break; + default: + aRet = "?"; + } + + return aRet; +} + +// field data + +ScFieldEditEngine::ScFieldEditEngine( + ScDocument* pDoc, SfxItemPool* pEnginePoolP, + SfxItemPool* pTextObjectPool, bool bDeleteEnginePoolP) : + ScEditEngineDefaulter( pEnginePoolP, bDeleteEnginePoolP ), + mpDoc(pDoc), bExecuteURL(true) +{ + if ( pTextObjectPool ) + SetEditTextObjectPool( pTextObjectPool ); + SetControlWord( EEControlBits(GetControlWord() | EEControlBits::MARKFIELDS) & ~EEControlBits::RTFSTYLESHEETS ); +} + +OUString ScFieldEditEngine::CalcFieldValue( const SvxFieldItem& rField, + sal_Int32 /* nPara */, sal_Int32 /* nPos */, + std::optional<Color>& rTxtColor, std::optional<Color>& /* rFldColor */, + std::optional<FontLineStyle>& rFldLineStyle ) +{ + const SvxFieldData* pFieldData = rField.GetField(); + + if (!pFieldData) + return " "; + + return ScEditUtil::GetCellFieldValue(*pFieldData, mpDoc, &rTxtColor, &rFldLineStyle); +} + +bool ScFieldEditEngine::FieldClicked( const SvxFieldItem& rField ) +{ + if (!bExecuteURL) + return false; + + if (const SvxURLField* pURLField = dynamic_cast<const SvxURLField*>(rField.GetField())) + { + ScGlobal::OpenURL(pURLField->GetURL(), pURLField->GetTargetFrame()); + return true; + } + return false; +} + +ScNoteEditEngine::ScNoteEditEngine( SfxItemPool* pEnginePoolP, + SfxItemPool* pTextObjectPool ) : + ScEditEngineDefaulter( pEnginePoolP, false/*bDeleteEnginePoolP*/ ) +{ + if ( pTextObjectPool ) + SetEditTextObjectPool( pTextObjectPool ); + SetControlWord( EEControlBits(GetControlWord() | EEControlBits::MARKFIELDS) & ~EEControlBits::RTFSTYLESHEETS ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/filtopt.cxx b/sc/source/core/tool/filtopt.cxx new file mode 100644 index 0000000000..13c856150d --- /dev/null +++ b/sc/source/core/tool/filtopt.cxx @@ -0,0 +1,74 @@ +/* -*- 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 <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <osl/diagnose.h> + +#include <filtopt.hxx> +#include <miscuno.hxx> + +using namespace utl; +using namespace css::uno; + +constexpr OUStringLiteral CFGPATH_FILTER = u"Office.Calc/Filter/Import"; + +#define SCFILTOPT_WK3 2 + +ScFilterOptions::ScFilterOptions() : + ConfigItem( CFGPATH_FILTER ), + bWK3Flag( false ) +{ + Sequence<OUString> aNames { "MS_Excel/ColScale", // SCFILTOPT_COLSCALE + "MS_Excel/RowScale", // SCFILTOPT_ROWSCALE + "Lotus123/WK3" }; // SCFILTOPT_WK3 + Sequence<Any> aValues = GetProperties(aNames); + const Any* pValues = aValues.getConstArray(); + OSL_ENSURE(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if(aValues.getLength() != aNames.getLength()) + return; + + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + OSL_ENSURE(pValues[nProp].hasValue(), "property value missing"); + if(pValues[nProp].hasValue()) + { + switch(nProp) + { + case SCFILTOPT_WK3: + bWK3Flag = ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ); + break; + } + } + } +} + +void ScFilterOptions::ImplCommit() +{ + // options are never modified from office + + OSL_FAIL("trying to commit changed ScFilterOptions?"); +} + +void ScFilterOptions::Notify( const Sequence<OUString>& /* aPropertyNames */ ) +{ + OSL_FAIL("properties have been changed"); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/formulagroup.cxx b/sc/source/core/tool/formulagroup.cxx new file mode 100644 index 0000000000..7b1965c2eb --- /dev/null +++ b/sc/source/core/tool/formulagroup.cxx @@ -0,0 +1,244 @@ +/* -*- 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/. + */ + +#include <config_feature_opencl.h> + +#include <formulagroup.hxx> +#include <formulagroupcl.hxx> +#include <document.hxx> +#include <formulacell.hxx> +#include <interpre.hxx> +#include <globalnames.hxx> + +#include <officecfg/Office/Common.hxx> +#if HAVE_FEATURE_OPENCL +#include <opencl/platforminfo.hxx> +#endif +#include <sal/log.hxx> + +#include <cstdio> +#include <limits> +#include <unordered_map> +#include <vector> + +#if HAVE_FEATURE_OPENCL +# include <opencl/openclwrapper.hxx> +#endif + +namespace sc { + +FormulaGroupEntry::FormulaGroupEntry( ScFormulaCell** pCells, size_t nRow, size_t nLength ) : + mpCells(pCells), mnRow(nRow), mnLength(nLength), mbShared(true) {} + +FormulaGroupEntry::FormulaGroupEntry( ScFormulaCell* pCell, size_t nRow ) : + mpCell(pCell), mnRow(nRow), mnLength(0), mbShared(false) {} + +size_t FormulaGroupContext::ColKey::Hash::operator ()( const FormulaGroupContext::ColKey& rKey ) const +{ + return rKey.mnTab * MAXCOLCOUNT_JUMBO + rKey.mnCol; +} + +FormulaGroupContext::ColKey::ColKey( SCTAB nTab, SCCOL nCol ) : mnTab(nTab), mnCol(nCol) {} + +bool FormulaGroupContext::ColKey::operator== ( const ColKey& r ) const +{ + return mnTab == r.mnTab && mnCol == r.mnCol; +} + +FormulaGroupContext::ColArray::ColArray( NumArrayType* pNumArray, StrArrayType* pStrArray ) : + mpNumArray(pNumArray), mpStrArray(pStrArray), mnSize(0) +{ + if (mpNumArray) + mnSize = mpNumArray->size(); + else if (mpStrArray) + mnSize = mpStrArray->size(); +} + +FormulaGroupContext::ColArray* FormulaGroupContext::getCachedColArray( SCTAB nTab, SCCOL nCol, size_t nSize ) +{ + ColArraysType::iterator itColArray = maColArrays.find(ColKey(nTab, nCol)); + if (itColArray == maColArrays.end()) + // Not cached for this column. + return nullptr; + + ColArray& rCached = itColArray->second; + if (nSize > rCached.mnSize) + // Cached data array is not long enough for the requested range. + return nullptr; + + return &rCached; +} + +FormulaGroupContext::ColArray* FormulaGroupContext::setCachedColArray( + SCTAB nTab, SCCOL nCol, NumArrayType* pNumArray, StrArrayType* pStrArray ) +{ + ColArraysType::iterator it = maColArrays.find(ColKey(nTab, nCol)); + if (it == maColArrays.end()) + { + std::pair<ColArraysType::iterator,bool> r = + maColArrays.emplace(ColKey(nTab, nCol), ColArray(pNumArray, pStrArray)); + + if (!r.second) + // Somehow the insertion failed. + return nullptr; + + return &r.first->second; + } + + // Prior array exists for this column. Overwrite it. + ColArray& rArray = it->second; + rArray = ColArray(pNumArray, pStrArray); + return &rArray; +} + +void FormulaGroupContext::discardCachedColArray( SCTAB nTab, SCCOL nCol ) +{ + ColArraysType::iterator itColArray = maColArrays.find(ColKey(nTab, nCol)); + if (itColArray != maColArrays.end()) + maColArrays.erase(itColArray); +} + +void FormulaGroupContext::ensureStrArray( ColArray& rColArray, size_t nArrayLen ) +{ + if (rColArray.mpStrArray) + return; + + m_StrArrays.push_back( + std::make_unique<sc::FormulaGroupContext::StrArrayType>(nArrayLen, nullptr)); + rColArray.mpStrArray = m_StrArrays.back().get(); +} + +void FormulaGroupContext::ensureNumArray( ColArray& rColArray, size_t nArrayLen ) +{ + if (rColArray.mpNumArray) + return; + + m_NumArrays.push_back( + std::make_unique<sc::FormulaGroupContext::NumArrayType>(nArrayLen, + std::numeric_limits<double>::quiet_NaN())); + rColArray.mpNumArray = m_NumArrays.back().get(); +} + +FormulaGroupContext::FormulaGroupContext() +{ +} + +FormulaGroupContext::~FormulaGroupContext() +{ +} + +CompiledFormula::CompiledFormula() {} + +CompiledFormula::~CompiledFormula() {} + +FormulaGroupInterpreter *FormulaGroupInterpreter::msInstance = nullptr; + +void FormulaGroupInterpreter::MergeCalcConfig(const ScDocument& rDoc) +{ + maCalcConfig = ScInterpreter::GetGlobalConfig(); + maCalcConfig.MergeDocumentSpecific(rDoc.GetCalcConfig()); +} + +/// load and/or configure the correct formula group interpreter +FormulaGroupInterpreter *FormulaGroupInterpreter::getStatic() +{ + if ( !msInstance ) + { +#if HAVE_FEATURE_OPENCL + if (ScCalcConfig::isOpenCLEnabled()) + { + const ScCalcConfig& rConfig = ScInterpreter::GetGlobalConfig(); + if( !switchOpenCLDevice(rConfig.maOpenCLDevice, rConfig.mbOpenCLAutoSelect)) + { + if( ScCalcConfig::getForceCalculationType() == ForceCalculationOpenCL ) + { + SAL_WARN( "opencl", "OpenCL forced but failed to initialize" ); + abort(); + } + } + } +#endif + } + + return msInstance; +} + +#if HAVE_FEATURE_OPENCL +void FormulaGroupInterpreter::fillOpenCLInfo(std::vector<OpenCLPlatformInfo>& rPlatforms) +{ + const std::vector<OpenCLPlatformInfo>& rPlatformsFromWrapper = + openclwrapper::fillOpenCLInfo(); + + rPlatforms.assign(rPlatformsFromWrapper.begin(), rPlatformsFromWrapper.end()); +} + +bool FormulaGroupInterpreter::switchOpenCLDevice(std::u16string_view rDeviceId, bool bAutoSelect, bool bForceEvaluation) +{ + bool bOpenCLEnabled = ScCalcConfig::isOpenCLEnabled(); + if (!bOpenCLEnabled || (rDeviceId == u"" OPENCL_SOFTWARE_DEVICE_CONFIG_NAME)) + { + delete msInstance; + msInstance = nullptr; + return false; + } + + OUString aSelectedCLDeviceVersionID; + bool bSuccess = openclwrapper::switchOpenCLDevice(rDeviceId, bAutoSelect, bForceEvaluation, aSelectedCLDeviceVersionID); + + if (!bSuccess) + return false; + + delete msInstance; + msInstance = new sc::opencl::FormulaGroupInterpreterOpenCL(); + + return true; +} + +void FormulaGroupInterpreter::getOpenCLDeviceInfo(sal_Int32& rDeviceId, sal_Int32& rPlatformId) +{ + rDeviceId = -1; + rPlatformId = -1; + bool bOpenCLEnabled = ScCalcConfig::isOpenCLEnabled(); + if(!bOpenCLEnabled) + return; + + size_t aDeviceId = static_cast<size_t>(-1); + size_t aPlatformId = static_cast<size_t>(-1); + + openclwrapper::getOpenCLDeviceInfo(aDeviceId, aPlatformId); + rDeviceId = aDeviceId; + rPlatformId = aPlatformId; +} + +void FormulaGroupInterpreter::enableOpenCL_UnitTestsOnly() +{ + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Misc::UseOpenCL::set(true, batch); + batch->commit(); + + ScCalcConfig aConfig = ScInterpreter::GetGlobalConfig(); + + aConfig.mbOpenCLSubsetOnly = false; + aConfig.mnOpenCLMinimumFormulaGroupSize = 2; + + ScInterpreter::SetGlobalConfig(aConfig); +} + +void FormulaGroupInterpreter::disableOpenCL_UnitTestsOnly() +{ + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Misc::UseOpenCL::set(false, batch); + batch->commit(); +} + +#endif + +} // namespace sc + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/formulalogger.cxx b/sc/source/core/tool/formulalogger.cxx new file mode 100644 index 0000000000..a5c01a596b --- /dev/null +++ b/sc/source/core/tool/formulalogger.cxx @@ -0,0 +1,352 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + */ + +#include <formulalogger.hxx> +#include <formulacell.hxx> +#include <tokenarray.hxx> +#include <document.hxx> +#include <docsh.hxx> +#include <tokenstringcontext.hxx> +#include <address.hxx> +#include <interpre.hxx> + +#include <osl/file.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/docfile.hxx> +#include <tools/urlobj.hxx> +#include <formula/vectortoken.hxx> +#include <rtl/ustrbuf.hxx> + +#include <cstdlib> +#include <utility> + +namespace sc { + +namespace { + +std::unique_ptr<osl::File> initFile() +{ + const char* pPath = std::getenv("LIBO_FORMULA_LOG_FILE"); + if (!pPath) + return nullptr; + + // Support both file:///... and system file path notations. + OUString aPath = OUString::createFromAscii(pPath); + INetURLObject aURL; + aURL.SetSmartURL(aPath); + aPath = aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE); + + return std::make_unique<osl::File>(aPath); +} + +ScRefFlags getRefFlags( const ScAddress& rCellPos, const ScAddress& rRefPos ) +{ + ScRefFlags eFlags = ScRefFlags::VALID; + if (rCellPos.Tab() != rRefPos.Tab()) + eFlags |= ScRefFlags::TAB_3D; + return eFlags; +} + +} + +FormulaLogger& FormulaLogger::get() +{ + static FormulaLogger aLogger; + return aLogger; +} + +struct FormulaLogger::GroupScope::Impl +{ + FormulaLogger& mrLogger; + const ScDocument& mrDoc; + + OUString maPrefix; + std::vector<OUString> maMessages; + + bool mbCalcComplete; + bool mbOutputEnabled; + + Impl( FormulaLogger& rLogger, OUString aPrefix, const ScDocument& rDoc, + const ScFormulaCell& rCell, bool bOutputEnabled ) : + mrLogger(rLogger), mrDoc(rDoc), maPrefix(std::move(aPrefix)), + mbCalcComplete(false), mbOutputEnabled(bOutputEnabled) + { + ++mrLogger.mnNestLevel; + + if (!mbOutputEnabled) + return; + + sc::TokenStringContext aCxt(rDoc, rDoc.GetGrammar()); + OUString aFormula = rCell.GetCode()->CreateString(aCxt, rCell.aPos); + + mrLogger.write(maPrefix); + mrLogger.writeNestLevel(); + + mrLogger.writeAscii("-- enter (formula='"); + mrLogger.write(aFormula); + mrLogger.writeAscii("', size="); + mrLogger.write(rCell.GetSharedLength()); + mrLogger.writeAscii(")\n"); + } + + ~Impl() + { + if (mbOutputEnabled) + { + for (const OUString& rMsg : maMessages) + { + mrLogger.write(maPrefix); + mrLogger.writeNestLevel(); + mrLogger.writeAscii(" * "); + mrLogger.write(rMsg); + mrLogger.writeAscii("\n"); + } + + mrLogger.write(maPrefix); + mrLogger.writeNestLevel(); + mrLogger.writeAscii("-- exit ("); + if (mbCalcComplete) + mrLogger.writeAscii("calculation complete"); + else + mrLogger.writeAscii("without calculation"); + + mrLogger.writeAscii(")\n"); + + mrLogger.sync(); + } + + --mrLogger.mnNestLevel; + } +}; + +FormulaLogger::GroupScope::GroupScope( + FormulaLogger& rLogger, const OUString& rPrefix, const ScDocument& rDoc, + const ScFormulaCell& rCell, bool bOutputEnabled ) : + mpImpl(std::make_unique<Impl>(rLogger, rPrefix, rDoc, rCell, bOutputEnabled)) {} + +FormulaLogger::GroupScope::GroupScope(GroupScope&& r) noexcept : mpImpl(std::move(r.mpImpl)) {} + +FormulaLogger::GroupScope::~GroupScope() {} + +void FormulaLogger::GroupScope::addMessage( const OUString& rMsg ) +{ + mpImpl->maMessages.push_back(rMsg); +} + +void FormulaLogger::GroupScope::addRefMessage( + const ScAddress& rCellPos, const ScAddress& rRefPos, size_t nLen, + const formula::VectorRefArray& rArray ) +{ + ScRange aRefRange(rRefPos); + aRefRange.aEnd.IncRow(nLen-1); + OUString aRangeStr = aRefRange.Format(mpImpl->mrDoc, getRefFlags(rCellPos, rRefPos)); + + std::u16string_view aMsg; + if (rArray.mpNumericArray) + { + if (rArray.mpStringArray) + { + // mixture of numeric and string cells. + aMsg = u"numeric and string"; + } + else + { + // numeric cells only. + aMsg = u"numeric only"; + } + } + else + { + if (rArray.mpStringArray) + { + // string cells only. + aMsg = u"string only"; + } + else + { + // empty cells. + aMsg = u"empty"; + } + } + + mpImpl->maMessages.push_back(aRangeStr + ": " + aMsg); +} + +void FormulaLogger::GroupScope::addRefMessage( + const ScAddress& rCellPos, const ScAddress& rRefPos, size_t nLen, + const std::vector<formula::VectorRefArray>& rArrays ) +{ + ScAddress aPos(rRefPos); // copy + for (const formula::VectorRefArray& rArray : rArrays) + { + addRefMessage(rCellPos, aPos, nLen, rArray); + aPos.IncCol(); + } +} + +void FormulaLogger::GroupScope::addRefMessage( + const ScAddress& rCellPos, const ScAddress& rRefPos, + const formula::FormulaToken& rToken ) +{ + OUString aPosStr = rRefPos.Format(getRefFlags(rCellPos, rRefPos), &mpImpl->mrDoc); + std::u16string_view aMsg; + switch (rToken.GetType()) + { + case formula::svDouble: + aMsg = u"numeric value"; + break; + case formula::svString: + aMsg = u"string value"; + break; + default: + aMsg = u"unknown value"; + } + + mpImpl->maMessages.push_back(aPosStr + ": " + aMsg); +} + +void FormulaLogger::GroupScope::addGroupSizeThresholdMessage( const ScFormulaCell& rCell ) +{ + OUString aBuf = "group length below minimum threshold (" + + OUString::number(rCell.GetWeight()) + + " < " + + OUString::number(ScInterpreter::GetGlobalConfig().mnOpenCLMinimumFormulaGroupSize) + + ")"; + mpImpl->maMessages.push_back(aBuf); +} + +void FormulaLogger::GroupScope::setCalcComplete() +{ + mpImpl->mbCalcComplete = true; + addMessage("calculation performed"); +} + +FormulaLogger::FormulaLogger() +{ + mpLogFile = initFile(); + + if (!mpLogFile) + return; + + osl::FileBase::RC eRC = mpLogFile->open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create); + + if (eRC == osl::FileBase::E_EXIST) + { + eRC = mpLogFile->open(osl_File_OpenFlag_Write); + + if (eRC != osl::FileBase::E_None) + { + // Failed to open an existing log file. + mpLogFile.reset(); + return; + } + + if (mpLogFile->setPos(osl_Pos_End, 0) != osl::FileBase::E_None) + { + // Failed to set the position to the end of the file. + mpLogFile.reset(); + return; + } + } + else if (eRC != osl::FileBase::E_None) + { + // Failed to create a new file. + mpLogFile.reset(); + return; + } + + // Output the header information. + writeAscii("---\n"); + writeAscii("OpenCL: "); + writeAscii(ScCalcConfig::isOpenCLEnabled() ? "enabled\n" : "disabled\n"); + writeAscii("---\n"); + + sync(); +} + +FormulaLogger::~FormulaLogger() +{ + if (mpLogFile) + mpLogFile->close(); +} + +void FormulaLogger::writeAscii( const char* s ) +{ + if (!mpLogFile) + return; + + sal_uInt64 nBytes; + mpLogFile->write(s, strlen(s), nBytes); +} + +void FormulaLogger::writeAscii( const char* s, size_t n ) +{ + if (!mpLogFile) + return; + + sal_uInt64 nBytes; + mpLogFile->write(s, n, nBytes); +} + +void FormulaLogger::write( std::u16string_view ou ) +{ + OString s = OUStringToOString(ou, RTL_TEXTENCODING_UTF8); + writeAscii(s.getStr(), s.getLength()); +} + +void FormulaLogger::write( sal_Int32 n ) +{ + OString s = OString::number(n); + writeAscii(s.getStr(), s.getLength()); +} + +void FormulaLogger::sync() +{ + if (!mpLogFile) + return; + + mpLogFile->sync(); +} + +void FormulaLogger::writeNestLevel() +{ + // Write the nest level, but keep it only 1-character length to avoid + // messing up the spacing. + if (mnNestLevel < 10) + write(mnNestLevel); + else + writeAscii("!"); + + writeAscii(": "); + for (sal_Int32 i = 1; i < mnNestLevel; ++i) + writeAscii(" "); +} + +FormulaLogger::GroupScope FormulaLogger::enterGroup( + const ScDocument& rDoc, const ScFormulaCell& rCell ) +{ + // Get the file name if available. + const ScDocShell* pShell = rDoc.GetDocumentShell(); + const SfxMedium* pMedium = pShell ? pShell->GetMedium() : nullptr; + OUString aName; + if (pMedium) + aName = pMedium->GetURLObject().GetLastName(); + if (aName.isEmpty()) + aName = "-"; // unsaved document. + + OUString aGroupPrefix = aName + ": formula-group: " + + rCell.aPos.Format(ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDoc, rDoc.GetAddressConvention()) + ": "; + + bool bOutputEnabled = mpLastGroup != rCell.GetCellGroup().get(); + mpLastGroup = rCell.GetCellGroup().get(); + + return GroupScope(*this, aGroupPrefix, rDoc, rCell, bOutputEnabled); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/formulaopt.cxx b/sc/source/core/tool/formulaopt.cxx new file mode 100644 index 0000000000..08c660a44a --- /dev/null +++ b/sc/source/core/tool/formulaopt.cxx @@ -0,0 +1,653 @@ +/* -*- 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/. + */ + +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/lang/Locale.hpp> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <unotools/localedatawrapper.hxx> +#include <formulaopt.hxx> +#include <global.hxx> +#include <formulagroup.hxx> +#include <sc.hrc> +#include <utility> + +using namespace utl; +using namespace com::sun::star::uno; +namespace lang = ::com::sun::star::lang; + + +ScFormulaOptions::ScFormulaOptions() +{ + SetDefaults(); +} + +void ScFormulaOptions::SetDefaults() +{ + bUseEnglishFuncName = false; + eFormulaGrammar = ::formula::FormulaGrammar::GRAM_NATIVE; + mbWriteCalcConfig = true; + meOOXMLRecalc = RECALC_ASK; + meODFRecalc = RECALC_ASK; + + // unspecified means use the current formula syntax. + aCalcConfig.reset(); + + ResetFormulaSeparators(); +} + +void ScFormulaOptions::ResetFormulaSeparators() +{ + GetDefaultFormulaSeparators(aFormulaSepArg, aFormulaSepArrayCol, aFormulaSepArrayRow); +} + +void ScFormulaOptions::GetDefaultFormulaSeparators( + OUString& rSepArg, OUString& rSepArrayCol, OUString& rSepArrayRow) +{ + // Defaults to the old separator values. + rSepArg = ";"; + rSepArrayCol = ";"; + rSepArrayRow = "|"; + + const lang::Locale& rLocale = ScGlobal::GetLocale(); + const OUString& rLang = rLocale.Language; + if (rLang == "ru") + // Don't do automatic guess for these languages, and fall back to + // the old separator set. + return; + + const LocaleDataWrapper& rLocaleData = ScGlobal::getLocaleData(); + const OUString& rDecSep = rLocaleData.getNumDecimalSep(); + const OUString& rListSep = rLocaleData.getListSep(); + + if (rDecSep.isEmpty() || rListSep.isEmpty()) + // Something is wrong. Stick with the default separators. + return; + + sal_Unicode cDecSep = rDecSep[0]; + sal_Unicode cListSep = rListSep[0]; + sal_Unicode cDecSepAlt = rLocaleData.getNumDecimalSepAlt().toChar(); // usually 0 (empty) + + // Excel by default uses system's list separator as the parameter + // separator, which in English locales is a comma. However, OOo's list + // separator value is set to ';' for all English locales. Because of this + // discrepancy, we will hardcode the separator value here, for now. + // Similar for decimal separator alternative. + // However, if the decimal separator alternative is '.' and the decimal + // separator is ',' this makes no sense, fall back to ';' in that case. + if (cDecSep == '.' || (cDecSepAlt == '.' && cDecSep != ',')) + cListSep = ','; + else if (cDecSep == ',' && cDecSepAlt == '.') + cListSep = ';'; + + // Special case for de_CH locale. + if (rLocale.Language == "de" && rLocale.Country == "CH") + cListSep = ';'; + + // by default, the parameter separator equals the locale-specific + // list separator. + rSepArg = OUString(cListSep); + + if (cDecSep == cListSep && cDecSep != ';') + // if the decimal and list separators are equal, set the + // parameter separator to be ';', unless they are both + // semicolon in which case don't change the decimal separator. + rSepArg = ";"; + + rSepArrayCol = ","; + if (cDecSep == ',') + rSepArrayCol = "."; + rSepArrayRow = ";"; +} + +bool ScFormulaOptions::operator==( const ScFormulaOptions& rOpt ) const +{ + return bUseEnglishFuncName == rOpt.bUseEnglishFuncName + && eFormulaGrammar == rOpt.eFormulaGrammar + && aCalcConfig == rOpt.aCalcConfig + && mbWriteCalcConfig == rOpt.mbWriteCalcConfig + && aFormulaSepArg == rOpt.aFormulaSepArg + && aFormulaSepArrayRow == rOpt.aFormulaSepArrayRow + && aFormulaSepArrayCol == rOpt.aFormulaSepArrayCol + && meOOXMLRecalc == rOpt.meOOXMLRecalc + && meODFRecalc == rOpt.meODFRecalc; +} + +bool ScFormulaOptions::operator!=( const ScFormulaOptions& rOpt ) const +{ + return !(operator==(rOpt)); +} + +ScTpFormulaItem::ScTpFormulaItem( ScFormulaOptions aOpt ) : + SfxPoolItem ( SID_SCFORMULAOPTIONS ), + theOptions (std::move( aOpt )) +{ +} + +ScTpFormulaItem::~ScTpFormulaItem() +{ +} + +bool ScTpFormulaItem::operator==( const SfxPoolItem& rItem ) const +{ + assert(SfxPoolItem::operator==(rItem)); + + const ScTpFormulaItem& rPItem = static_cast<const ScTpFormulaItem&>(rItem); + return ( theOptions == rPItem.theOptions ); +} + +ScTpFormulaItem* ScTpFormulaItem::Clone( SfxItemPool * ) const +{ + return new ScTpFormulaItem( *this ); +} + +constexpr OUStringLiteral CFGPATH_FORMULA = u"Office.Calc/Formula"; + +#define SCFORMULAOPT_GRAMMAR 0 +#define SCFORMULAOPT_ENGLISH_FUNCNAME 1 +#define SCFORMULAOPT_SEP_ARG 2 +#define SCFORMULAOPT_SEP_ARRAY_ROW 3 +#define SCFORMULAOPT_SEP_ARRAY_COL 4 +#define SCFORMULAOPT_STRING_REF_SYNTAX 5 +#define SCFORMULAOPT_STRING_CONVERSION 6 +#define SCFORMULAOPT_EMPTY_OUSTRING_AS_ZERO 7 +#define SCFORMULAOPT_OOXML_RECALC 8 +#define SCFORMULAOPT_ODF_RECALC 9 +#define SCFORMULAOPT_OPENCL_AUTOSELECT 10 +#define SCFORMULAOPT_OPENCL_DEVICE 11 +#define SCFORMULAOPT_OPENCL_SUBSET_ONLY 12 +#define SCFORMULAOPT_OPENCL_MIN_SIZE 13 +#define SCFORMULAOPT_OPENCL_SUBSET_OPS 14 + +Sequence<OUString> ScFormulaCfg::GetPropertyNames() +{ + return {"Syntax/Grammar", // SCFORMULAOPT_GRAMMAR + "Syntax/EnglishFunctionName", // SCFORMULAOPT_ENGLISH_FUNCNAME + "Syntax/SeparatorArg", // SCFORMULAOPT_SEP_ARG + "Syntax/SeparatorArrayRow", // SCFORMULAOPT_SEP_ARRAY_ROW + "Syntax/SeparatorArrayCol", // SCFORMULAOPT_SEP_ARRAY_COL + "Syntax/StringRefAddressSyntax", // SCFORMULAOPT_STRING_REF_SYNTAX + "Syntax/StringConversion", // SCFORMULAOPT_STRING_CONVERSION + "Syntax/EmptyStringAsZero", // SCFORMULAOPT_EMPTY_OUSTRING_AS_ZERO + "Load/OOXMLRecalcMode", // SCFORMULAOPT_OOXML_RECALC + "Load/ODFRecalcMode", // SCFORMULAOPT_ODF_RECALC + "Calculation/OpenCLAutoSelect", // SCFORMULAOPT_OPENCL_AUTOSELECT + "Calculation/OpenCLDevice", // SCFORMULAOPT_OPENCL_DEVICE + "Calculation/OpenCLSubsetOnly", // SCFORMULAOPT_OPENCL_SUBSET_ONLY + "Calculation/OpenCLMinimumDataSize", // SCFORMULAOPT_OPENCL_MIN_SIZE + "Calculation/OpenCLSubsetOpCodes"}; // SCFORMULAOPT_OPENCL_SUBSET_OPS +} + +ScFormulaCfg::PropsToIds ScFormulaCfg::GetPropNamesToId() +{ + Sequence<OUString> aPropNames = GetPropertyNames(); + static sal_uInt16 aVals[] = { + SCFORMULAOPT_GRAMMAR, + SCFORMULAOPT_ENGLISH_FUNCNAME, + SCFORMULAOPT_SEP_ARG, + SCFORMULAOPT_SEP_ARRAY_ROW, + SCFORMULAOPT_SEP_ARRAY_COL, + SCFORMULAOPT_STRING_REF_SYNTAX, + SCFORMULAOPT_STRING_CONVERSION, + SCFORMULAOPT_EMPTY_OUSTRING_AS_ZERO, + SCFORMULAOPT_OOXML_RECALC, + SCFORMULAOPT_ODF_RECALC, + SCFORMULAOPT_OPENCL_AUTOSELECT, + SCFORMULAOPT_OPENCL_DEVICE, + SCFORMULAOPT_OPENCL_SUBSET_ONLY, + SCFORMULAOPT_OPENCL_MIN_SIZE, + SCFORMULAOPT_OPENCL_SUBSET_OPS, + }; + OSL_ENSURE( SAL_N_ELEMENTS(aVals) == aPropNames.getLength(), "Properties and ids are out of Sync"); + PropsToIds aPropIdMap; + for ( sal_Int32 i=0; i<aPropNames.getLength(); ++i ) + aPropIdMap[aPropNames[i]] = aVals[ i ]; + return aPropIdMap; +} + +ScFormulaCfg::ScFormulaCfg() : + ConfigItem( CFGPATH_FORMULA ) +{ + Sequence<OUString> aNames = GetPropertyNames(); + UpdateFromProperties( aNames ); + EnableNotification( aNames ); +} + +void ScFormulaCfg::UpdateFromProperties( const Sequence<OUString>& aNames ) +{ + Sequence<Any> aValues = GetProperties(aNames); + const Any* pValues = aValues.getConstArray(); + OSL_ENSURE(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + PropsToIds aPropMap = GetPropNamesToId(); + if(aValues.getLength() != aNames.getLength()) + return; + + sal_Int32 nIntVal = 0; + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + PropsToIds::iterator it_end = aPropMap.end(); + PropsToIds::iterator it = aPropMap.find( aNames[nProp] ); + if(pValues[nProp].hasValue() && it != it_end ) + { + switch(it->second) + { + case SCFORMULAOPT_GRAMMAR: + { + // Get default value in case this option is not set. + ::formula::FormulaGrammar::Grammar eGram = GetFormulaSyntax(); + + do + { + if (!(pValues[nProp] >>= nIntVal)) + // extracting failed. + break; + + switch (nIntVal) + { + case 0: // Calc A1 + eGram = ::formula::FormulaGrammar::GRAM_NATIVE; + break; + case 1: // Excel A1 + eGram = ::formula::FormulaGrammar::GRAM_NATIVE_XL_A1; + break; + case 2: // Excel R1C1 + eGram = ::formula::FormulaGrammar::GRAM_NATIVE_XL_R1C1; + break; + default: + ; + } + } + while (false); + SetFormulaSyntax(eGram); + } + break; + case SCFORMULAOPT_ENGLISH_FUNCNAME: + { + bool bEnglish = false; + if (pValues[nProp] >>= bEnglish) + SetUseEnglishFuncName(bEnglish); + } + break; + case SCFORMULAOPT_SEP_ARG: + { + OUString aSep; + if ((pValues[nProp] >>= aSep) && !aSep.isEmpty()) + SetFormulaSepArg(aSep); + } + break; + case SCFORMULAOPT_SEP_ARRAY_ROW: + { + OUString aSep; + if ((pValues[nProp] >>= aSep) && !aSep.isEmpty()) + SetFormulaSepArrayRow(aSep); + } + break; + case SCFORMULAOPT_SEP_ARRAY_COL: + { + OUString aSep; + if ((pValues[nProp] >>= aSep) && !aSep.isEmpty()) + SetFormulaSepArrayCol(aSep); + } + break; + case SCFORMULAOPT_STRING_REF_SYNTAX: + { + // Get default value in case this option is not set. + ::formula::FormulaGrammar::AddressConvention eConv = GetCalcConfig().meStringRefAddressSyntax; + + do + { + if (!(pValues[nProp] >>= nIntVal)) + // extraction failed. + break; + + switch (nIntVal) + { + case -1: // Same as the formula grammar. + eConv = formula::FormulaGrammar::CONV_UNSPECIFIED; + break; + case 0: // Calc A1 + eConv = formula::FormulaGrammar::CONV_OOO; + break; + case 1: // Excel A1 + eConv = formula::FormulaGrammar::CONV_XL_A1; + break; + case 2: // Excel R1C1 + eConv = formula::FormulaGrammar::CONV_XL_R1C1; + break; + case 3: // Calc A1 | Excel A1 + eConv = formula::FormulaGrammar::CONV_A1_XL_A1; + break; + default: + ; + } + } + while (false); + GetCalcConfig().meStringRefAddressSyntax = eConv; + } + break; + case SCFORMULAOPT_STRING_CONVERSION: + { + // Get default value in case this option is not set. + ScCalcConfig::StringConversion eConv = GetCalcConfig().meStringConversion; + + do + { + if (!(pValues[nProp] >>= nIntVal)) + // extraction failed. + break; + + switch (nIntVal) + { + case 0: + eConv = ScCalcConfig::StringConversion::ILLEGAL; + break; + case 1: + eConv = ScCalcConfig::StringConversion::ZERO; + break; + case 2: + eConv = ScCalcConfig::StringConversion::UNAMBIGUOUS; + break; + case 3: + eConv = ScCalcConfig::StringConversion::LOCALE; + break; + default: + SAL_WARN("sc", "unknown string conversion option!"); + } + } + while (false); + GetCalcConfig().meStringConversion = eConv; + } + break; + case SCFORMULAOPT_EMPTY_OUSTRING_AS_ZERO: + { + bool bVal = GetCalcConfig().mbEmptyStringAsZero; + pValues[nProp] >>= bVal; + GetCalcConfig().mbEmptyStringAsZero = bVal; + } + break; + case SCFORMULAOPT_OOXML_RECALC: + { + ScRecalcOptions eOpt = RECALC_ASK; + if (pValues[nProp] >>= nIntVal) + { + switch (nIntVal) + { + case 0: + eOpt = RECALC_ALWAYS; + break; + case 1: + eOpt = RECALC_NEVER; + break; + case 2: + eOpt = RECALC_ASK; + break; + default: + SAL_WARN("sc", "unknown ooxml recalc option!"); + } + } + + SetOOXMLRecalcOptions(eOpt); + } + break; + case SCFORMULAOPT_ODF_RECALC: + { + ScRecalcOptions eOpt = RECALC_ASK; + if (pValues[nProp] >>= nIntVal) + { + switch (nIntVal) + { + case 0: + eOpt = RECALC_ALWAYS; + break; + case 1: + eOpt = RECALC_NEVER; + break; + case 2: + eOpt = RECALC_ASK; + break; + default: + SAL_WARN("sc", "unknown odf recalc option!"); + } + } + + SetODFRecalcOptions(eOpt); + } + break; + case SCFORMULAOPT_OPENCL_AUTOSELECT: + { + bool bVal = GetCalcConfig().mbOpenCLAutoSelect; + pValues[nProp] >>= bVal; + GetCalcConfig().mbOpenCLAutoSelect = bVal; + } + break; + case SCFORMULAOPT_OPENCL_DEVICE: + { + OUString aOpenCLDevice = GetCalcConfig().maOpenCLDevice; + pValues[nProp] >>= aOpenCLDevice; + GetCalcConfig().maOpenCLDevice = aOpenCLDevice; + } + break; + case SCFORMULAOPT_OPENCL_SUBSET_ONLY: + { + bool bVal = GetCalcConfig().mbOpenCLSubsetOnly; + pValues[nProp] >>= bVal; + GetCalcConfig().mbOpenCLSubsetOnly = bVal; + } + break; + case SCFORMULAOPT_OPENCL_MIN_SIZE: + { + sal_Int32 nVal = GetCalcConfig().mnOpenCLMinimumFormulaGroupSize; + pValues[nProp] >>= nVal; + GetCalcConfig().mnOpenCLMinimumFormulaGroupSize = nVal; + } + break; + case SCFORMULAOPT_OPENCL_SUBSET_OPS: + { + OUString sVal = ScOpCodeSetToSymbolicString(GetCalcConfig().mpOpenCLSubsetOpCodes); + pValues[nProp] >>= sVal; + GetCalcConfig().mpOpenCLSubsetOpCodes = ScStringToOpCodeSet(sVal); + } + break; + } + } + } +} + +void ScFormulaCfg::ImplCommit() +{ + Sequence<OUString> aNames = GetPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + Sequence<Any> aOldValues = GetProperties(aNames); + Any* pOldValues = aOldValues.getArray(); + + bool bSetOpenCL = false; + + for (int nProp = 0; nProp < aNames.getLength(); ++nProp) + { + switch (nProp) + { + case SCFORMULAOPT_GRAMMAR : + { + sal_Int32 nVal = 0; + switch (GetFormulaSyntax()) + { + case ::formula::FormulaGrammar::GRAM_NATIVE_XL_A1: nVal = 1; break; + case ::formula::FormulaGrammar::GRAM_NATIVE_XL_R1C1: nVal = 2; break; + default: break; + } + pValues[nProp] <<= nVal; + } + break; + case SCFORMULAOPT_ENGLISH_FUNCNAME: + { + bool b = GetUseEnglishFuncName(); + pValues[nProp] <<= b; + } + break; + case SCFORMULAOPT_SEP_ARG: + pValues[nProp] <<= GetFormulaSepArg(); + break; + case SCFORMULAOPT_SEP_ARRAY_ROW: + pValues[nProp] <<= GetFormulaSepArrayRow(); + break; + case SCFORMULAOPT_SEP_ARRAY_COL: + pValues[nProp] <<= GetFormulaSepArrayCol(); + break; + case SCFORMULAOPT_STRING_REF_SYNTAX: + { + sal_Int32 nVal = -1; + + if (GetWriteCalcConfig()) + { + switch (GetCalcConfig().meStringRefAddressSyntax) + { + case ::formula::FormulaGrammar::CONV_OOO: nVal = 0; break; + case ::formula::FormulaGrammar::CONV_XL_A1: nVal = 1; break; + case ::formula::FormulaGrammar::CONV_XL_R1C1: nVal = 2; break; + case ::formula::FormulaGrammar::CONV_A1_XL_A1: nVal = 3; break; + default: break; + } + pValues[nProp] <<= nVal; + } + else + { + pValues[nProp] = pOldValues[nProp]; + } + } + break; + case SCFORMULAOPT_STRING_CONVERSION: + { + if (GetWriteCalcConfig()) + { + sal_Int32 nVal = 3; + + switch (GetCalcConfig().meStringConversion) + { + case ScCalcConfig::StringConversion::ILLEGAL: nVal = 0; break; + case ScCalcConfig::StringConversion::ZERO: nVal = 1; break; + case ScCalcConfig::StringConversion::UNAMBIGUOUS: nVal = 2; break; + case ScCalcConfig::StringConversion::LOCALE: nVal = 3; break; + } + pValues[nProp] <<= nVal; + } + else + { + pValues[nProp] = pOldValues[nProp]; + } + } + break; + case SCFORMULAOPT_EMPTY_OUSTRING_AS_ZERO: + { + if (GetWriteCalcConfig()) + { + bool bVal = GetCalcConfig().mbEmptyStringAsZero; + pValues[nProp] <<= bVal; + } + else + { + pValues[nProp] = pOldValues[nProp]; + } + } + break; + case SCFORMULAOPT_OOXML_RECALC: + { + sal_Int32 nVal = 2; + switch (GetOOXMLRecalcOptions()) + { + case RECALC_ALWAYS: + nVal = 0; + break; + case RECALC_NEVER: + nVal = 1; + break; + case RECALC_ASK: + nVal = 2; + break; + } + + pValues[nProp] <<= nVal; + } + break; + case SCFORMULAOPT_ODF_RECALC: + { + sal_Int32 nVal = 2; + switch (GetODFRecalcOptions()) + { + case RECALC_ALWAYS: + nVal = 0; + break; + case RECALC_NEVER: + nVal = 1; + break; + case RECALC_ASK: + nVal = 2; + break; + } + + pValues[nProp] <<= nVal; + } + break; + case SCFORMULAOPT_OPENCL_AUTOSELECT: + { + bool bVal = GetCalcConfig().mbOpenCLAutoSelect; + pValues[nProp] <<= bVal; + bSetOpenCL = true; + } + break; + case SCFORMULAOPT_OPENCL_DEVICE: + { + OUString aOpenCLDevice = GetCalcConfig().maOpenCLDevice; + pValues[nProp] <<= aOpenCLDevice; + bSetOpenCL = true; + } + break; + case SCFORMULAOPT_OPENCL_SUBSET_ONLY: + { + bool bVal = GetCalcConfig().mbOpenCLSubsetOnly; + pValues[nProp] <<= bVal; + } + break; + case SCFORMULAOPT_OPENCL_MIN_SIZE: + { + sal_Int32 nVal = GetCalcConfig().mnOpenCLMinimumFormulaGroupSize; + pValues[nProp] <<= nVal; + } + break; + case SCFORMULAOPT_OPENCL_SUBSET_OPS: + { + OUString sVal = ScOpCodeSetToSymbolicString(GetCalcConfig().mpOpenCLSubsetOpCodes); + pValues[nProp] <<= sVal; + } + break; + } + } +#if !HAVE_FEATURE_OPENCL + (void) bSetOpenCL; +#else + if(bSetOpenCL) + sc::FormulaGroupInterpreter::switchOpenCLDevice( + GetCalcConfig().maOpenCLDevice, GetCalcConfig().mbOpenCLAutoSelect); +#endif + PutProperties(aNames, aValues); +} + +void ScFormulaCfg::SetOptions( const ScFormulaOptions& rNew ) +{ + *static_cast<ScFormulaOptions*>(this) = rNew; + SetModified(); +} + +void ScFormulaCfg::Notify( const css::uno::Sequence< OUString >& rNames) +{ + UpdateFromProperties( rNames ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/formulaparserpool.cxx b/sc/source/core/tool/formulaparserpool.cxx new file mode 100644 index 0000000000..6ac873042d --- /dev/null +++ b/sc/source/core/tool/formulaparserpool.cxx @@ -0,0 +1,143 @@ +/* -*- 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 <formulaparserpool.hxx> +#include <com/sun/star/container/XContentEnumerationAccess.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/XSingleComponentFactory.hpp> +#include <com/sun/star/sheet/XFilterFormulaParser.hpp> +#include <comphelper/processfactory.hxx> +#include <sfx2/objsh.hxx> +#include <document.hxx> +#include <docsh.hxx> + +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sheet; +using namespace ::com::sun::star::uno; + +namespace { + +class ScParserFactoryMap +{ +public: + explicit ScParserFactoryMap(); + + Reference< XFormulaParser > createFormulaParser( + const Reference< XComponent >& rxComponent, + const OUString& rNamespace ); + +private: + typedef std::unordered_map< + OUString, Reference< XSingleComponentFactory > > FactoryMap; + + Reference< XComponentContext > mxContext; /// Global component context. + FactoryMap maFactories; /// All parser factories, mapped by formula namespace. +}; + +ScParserFactoryMap::ScParserFactoryMap() : + mxContext( ::comphelper::getProcessComponentContext() ) +{ + if( !mxContext.is() ) + return; + + try + { + // enumerate all implementations of the FormulaParser service + Reference< XContentEnumerationAccess > xFactoryEA( mxContext->getServiceManager(), UNO_QUERY_THROW ); + Reference< XEnumeration > xEnum( xFactoryEA->createContentEnumeration( "com.sun.star.sheet.FilterFormulaParser" ), UNO_SET_THROW ); + while( xEnum->hasMoreElements() ) try // single try/catch for every element + { + // create an instance of the formula parser implementation + Reference< XSingleComponentFactory > xCompFactory( xEnum->nextElement(), UNO_QUERY_THROW ); + Reference< XFilterFormulaParser > xParser( xCompFactory->createInstanceWithContext( mxContext ), UNO_QUERY_THROW ); + + // store factory in the map + OUString aNamespace = xParser->getSupportedNamespace(); + if( !aNamespace.isEmpty() ) + maFactories[ aNamespace ] = xCompFactory; + } + catch( Exception& ) + { + } + } + catch( Exception& ) + { + } +} + +Reference< XFormulaParser > ScParserFactoryMap::createFormulaParser( + const Reference< XComponent >& rxComponent, const OUString& rNamespace ) +{ + Reference< XFormulaParser > xParser; + FactoryMap::const_iterator aIt = maFactories.find( rNamespace ); + if( aIt != maFactories.end() ) try + { + Sequence< Any > aArgs{ Any(rxComponent) }; + xParser.set( aIt->second->createInstanceWithArgumentsAndContext( aArgs, mxContext ), UNO_QUERY_THROW ); + } + catch( Exception& ) + { + } + return xParser; +} + +} // namespace + +ScFormulaParserPool::ScFormulaParserPool( const ScDocument& rDoc ) : + mrDoc( rDoc ) +{ +} + +ScFormulaParserPool::~ScFormulaParserPool() +{ +} + +bool ScFormulaParserPool::hasFormulaParser( const OUString& rNamespace ) +{ + return getFormulaParser( rNamespace ).is(); +} + +Reference< XFormulaParser > ScFormulaParserPool::getFormulaParser( const OUString& rNamespace ) +{ + // try to find an existing parser entry + ParserMap::iterator aIt = maParsers.find( rNamespace ); + if( aIt != maParsers.end() ) + return aIt->second; + + // always create a new entry in the map (even if the following initialization fails) + Reference< XFormulaParser >& rxParser = maParsers[ rNamespace ]; + + // try to create a new parser object + if( ScDocShell* pDocShell = mrDoc.GetDocumentShell() ) try + { + static ScParserFactoryMap theScParserFactoryMap; + + Reference< XComponent > xComponent( pDocShell->GetModel() ); + rxParser = theScParserFactoryMap.createFormulaParser( xComponent, rNamespace ); + } + catch( Exception& ) + { + } + return rxParser; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/formularesult.cxx b/sc/source/core/tool/formularesult.cxx new file mode 100644 index 0000000000..b421354550 --- /dev/null +++ b/sc/source/core/tool/formularesult.cxx @@ -0,0 +1,641 @@ +/* -*- 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/. + */ + +#include <formularesult.hxx> +#include <scmatrix.hxx> +#include <token.hxx> + +#include <sal/log.hxx> +#include <utility> + +namespace sc { + +FormulaResultValue::FormulaResultValue() : mfValue(0.0), meType(Invalid), mnError(FormulaError::NONE) {} +FormulaResultValue::FormulaResultValue( double fValue ) : mfValue(fValue), meType(Value), mnError(FormulaError::NONE) {} +FormulaResultValue::FormulaResultValue( svl::SharedString aStr, bool bMultiLine ) : mfValue(0.0), maString(std::move(aStr)), mbMultiLine(bMultiLine), meType(String), mnError(FormulaError::NONE) {} +FormulaResultValue::FormulaResultValue( FormulaError nErr ) : mfValue(0.0), meType(Error), mnError(nErr) {} + +} + +ScFormulaResult::ScFormulaResult() : + mpToken(nullptr), + mbToken(true), + mbEmpty(false), + mbEmptyDisplayedAsString(false), + mbValueCached(false), + meMultiline(MULTILINE_UNKNOWN), + mnError(FormulaError::NONE) {} + +ScFormulaResult::ScFormulaResult( const ScFormulaResult & r ) : + mbToken( r.mbToken), + mbEmpty( r.mbEmpty), + mbEmptyDisplayedAsString( r.mbEmptyDisplayedAsString), + mbValueCached( r.mbValueCached), + meMultiline( r.meMultiline), + mnError( r.mnError) +{ + if (mbToken) + { + mpToken = r.mpToken; + if (mpToken) + { + // Since matrix dimension and + // results are assigned to a matrix + // cell formula token we have to + // clone that instead of sharing it. + const ScMatrixFormulaCellToken* pMatFormula = + r.GetMatrixFormulaCellToken(); + if (pMatFormula) + mpToken = new ScMatrixFormulaCellToken( *pMatFormula); + mpToken->IncRef(); + } + } + else + mfValue = r.mfValue; +} + +ScFormulaResult::ScFormulaResult( const formula::FormulaToken* p ) : + mbToken(false), + mbEmpty(false), + mbEmptyDisplayedAsString(false), + mbValueCached(false), + meMultiline(MULTILINE_UNKNOWN), + mnError(FormulaError::NONE) +{ + SetToken( p); +} + +ScFormulaResult::~ScFormulaResult() +{ + if (mbToken && mpToken) + mpToken->DecRef(); +} + +void ScFormulaResult::ResetToDefaults() +{ + mnError = FormulaError::NONE; + mbEmpty = false; + mbEmptyDisplayedAsString = false; + meMultiline = MULTILINE_UNKNOWN; + mbValueCached = false; +} + +void ScFormulaResult::ResolveToken( const formula::FormulaToken * p ) +{ + ResetToDefaults(); + if (!p) + { + mpToken = p; + mbToken = true; + } + else + { + switch (p->GetType()) + { + case formula::svError: + mnError = p->GetError(); + p->DecRef(); + mbToken = false; + // set in case mnError is 0 now, which shouldn't happen but ... + mfValue = 0.0; + meMultiline = MULTILINE_FALSE; + break; + case formula::svEmptyCell: + mbEmpty = true; + mbEmptyDisplayedAsString = static_cast<const ScEmptyCellToken*>(p)->IsDisplayedAsString(); + p->DecRef(); + mbToken = false; + meMultiline = MULTILINE_FALSE; + // Take advantage of fast double result return for empty result token. + // by setting mfValue to 0 and turning on mbValueCached flag. + mfValue = 0.0; + mbValueCached = true; + break; + case formula::svDouble: + mfValue = p->GetDouble(); + p->DecRef(); + mbToken = false; + meMultiline = MULTILINE_FALSE; + mbValueCached = true; + break; + default: + mpToken = p; + mbToken = true; + } + } +} + +ScFormulaResult & ScFormulaResult::operator=( const ScFormulaResult & r ) +{ + Assign( r); + return *this; +} + +void ScFormulaResult::Assign( const ScFormulaResult & r ) +{ + if (this == &r) + return; + + // It is important to reset the value-cache flag to that of the source + // unconditionally. + mbValueCached = r.mbValueCached; + + if (r.mbEmpty) + { + if (mbToken && mpToken) + mpToken->DecRef(); + mbToken = false; + mbEmpty = true; + mbEmptyDisplayedAsString = r.mbEmptyDisplayedAsString; + meMultiline = r.meMultiline; + // here r.mfValue will be 0.0 which is ensured in ResolveToken(). + mfValue = 0.0; + } + else if (r.mbToken) + { + // Matrix formula cell token must be cloned, see copy-ctor. + const ScMatrixFormulaCellToken* pMatFormula = + r.GetMatrixFormulaCellToken(); + if (pMatFormula) + SetToken( new ScMatrixFormulaCellToken( *pMatFormula)); + else + SetToken( r.mpToken); + } + else + SetDouble( r.mfValue); + // If there was an error there will be an error, no matter what Set...() + // methods did. + SetResultError(r.mnError); +} + +void ScFormulaResult::SetToken( const formula::FormulaToken* p ) +{ + ResetToDefaults(); + if (p) + p->IncRef(); + // Handle a result obtained from the interpreter to be assigned to a matrix + // formula cell's ScMatrixFormulaCellToken. + ScMatrixFormulaCellToken* pMatFormula = GetMatrixFormulaCellTokenNonConst(); + if (pMatFormula) + { + const ScMatrixCellResultToken* pMatResult = + (p && p->GetType() == formula::svMatrixCell ? + dynamic_cast<const ScMatrixCellResultToken*>(p) : nullptr); + if (pMatResult) + { + const ScMatrixFormulaCellToken* pNewMatFormula = + dynamic_cast<const ScMatrixFormulaCellToken*>(pMatResult); + if (pNewMatFormula && (pMatFormula->GetMatCols() <= 0 || pMatFormula->GetMatRows() <= 0)) + { + SAL_WARN( "sc", "ScFormulaResult::SetToken: pNewMatFormula and pMatFormula, overriding matrix formula dimension; intended?"); + pMatFormula->SetMatColsRows( pNewMatFormula->GetMatCols(), + pNewMatFormula->GetMatRows()); + } + pMatFormula->Assign( *pMatResult); + p->DecRef(); + } + else if (p) + { + // This may be the result of some constant expression like + // {="string"} that doesn't result in a matrix but still would + // display the result in all cells of this matrix formula. + pMatFormula->Assign( *p); + p->DecRef(); + } + else + { + // NULL result? Well, if you say so ... + pMatFormula->ResetResult(); + } + } + else + { + if (mbToken && mpToken) + mpToken->DecRef(); + ResolveToken( p); + } +} + +void ScFormulaResult::SetDouble( double f ) +{ + ResetToDefaults(); + // Handle a result obtained from the interpreter to be assigned to a matrix + // formula cell's ScMatrixFormulaCellToken. + ScMatrixFormulaCellToken* pMatFormula = GetMatrixFormulaCellTokenNonConst(); + if (pMatFormula) + pMatFormula->SetUpperLeftDouble( f); + else + { + if (mbToken && mpToken) + mpToken->DecRef(); + mfValue = f; + mbToken = false; + meMultiline = MULTILINE_FALSE; + mbValueCached = true; + } +} + +formula::StackVar ScFormulaResult::GetType() const +{ + // Order is significant. + if (mnError != FormulaError::NONE) + return formula::svError; + if (mbEmpty) + return formula::svEmptyCell; + if (!mbToken) + return formula::svDouble; + if (mpToken) + return mpToken->GetType(); + return formula::svUnknown; +} + +formula::StackVar ScFormulaResult::GetCellResultType() const +{ + formula::StackVar sv = GetType(); + if (sv == formula::svMatrixCell) + // don't need to test for mpToken here, GetType() already did it + sv = static_cast<const ScMatrixCellResultToken*>(mpToken)->GetUpperLeftType(); + return sv; +} + +bool ScFormulaResult::IsEmptyDisplayedAsString() const +{ + if (mbEmpty) + return mbEmptyDisplayedAsString; + switch (GetType()) + { + case formula::svMatrixCell: + { + // don't need to test for mpToken here, GetType() already did it + const ScEmptyCellToken* p = dynamic_cast<const ScEmptyCellToken*>( + static_cast<const ScMatrixCellResultToken*>( + mpToken)->GetUpperLeftToken().get()); + if (p) + return p->IsDisplayedAsString(); + } + break; + case formula::svHybridCell: + { + const ScHybridCellToken* p = static_cast<const ScHybridCellToken*>(mpToken); + return p->IsEmptyDisplayedAsString(); + } + break; + default: + break; + } + return false; +} + +namespace { + +bool isValue( formula::StackVar sv ) +{ + return sv == formula::svDouble || sv == formula::svError + || sv == formula::svEmptyCell + // The initial uninitialized result value is double 0.0, even if the type + // is unknown, so the interpreter asking for it gets that double + // instead of having to convert a string which may result in #VALUE! + // (otherwise the unknown would be neither error nor double nor string) + || sv == formula::svUnknown; +} + +bool isString( formula::StackVar sv ) +{ + switch (sv) + { + case formula::svString: + case formula::svHybridCell: + return true; + default: + break; + } + + return false; +} + +} + +bool ScFormulaResult::IsValue() const +{ + if (IsEmptyDisplayedAsString()) + return true; + + return isValue(GetCellResultType()); +} + +bool ScFormulaResult::IsValueNoError() const +{ + switch (GetCellResultType()) + { + case formula::svDouble: + case formula::svEmptyCell: + return true; + default: + return false; + } +} + +bool ScFormulaResult::IsMultiline() const +{ + if (meMultiline == MULTILINE_UNKNOWN) + { + svl::SharedString aStr = GetString(); + if (!aStr.isEmpty() && aStr.getString().indexOf('\n') != -1) + const_cast<ScFormulaResult*>(this)->meMultiline = MULTILINE_TRUE; + else + const_cast<ScFormulaResult*>(this)->meMultiline = MULTILINE_FALSE; + } + return meMultiline == MULTILINE_TRUE; +} + +bool ScFormulaResult::GetErrorOrDouble( FormulaError& rErr, double& rVal ) const +{ + if (mbValueCached) + { + rVal = mfValue; + return true; + } + + if (mnError != FormulaError::NONE) + { + rErr = mnError; + return true; + } + + formula::StackVar sv = GetCellResultType(); + if (sv == formula::svError) + { + if (GetType() == formula::svMatrixCell) + { + // don't need to test for mpToken here, GetType() already did it + rErr = static_cast<const ScMatrixCellResultToken*>(mpToken)-> + GetUpperLeftToken()->GetError(); + } + else if (mpToken) + { + rErr = mpToken->GetError(); + } + } + + if (rErr != FormulaError::NONE) + return true; + + if (!isValue(sv)) + return false; + + rVal = GetDouble(); + return true; +} + +sc::FormulaResultValue ScFormulaResult::GetResult() const +{ + if (mbValueCached) + return sc::FormulaResultValue(mfValue); + + if (mnError != FormulaError::NONE) + return sc::FormulaResultValue(mnError); + + formula::StackVar sv = GetCellResultType(); + FormulaError nErr = FormulaError::NONE; + if (sv == formula::svError) + { + if (GetType() == formula::svMatrixCell) + { + // don't need to test for mpToken here, GetType() already did it + nErr = static_cast<const ScMatrixCellResultToken*>(mpToken)-> + GetUpperLeftToken()->GetError(); + } + else if (mpToken) + { + nErr = mpToken->GetError(); + } + } + + if (nErr != FormulaError::NONE) + return sc::FormulaResultValue(nErr); + + if (isValue(sv)) + return sc::FormulaResultValue(GetDouble()); + + if (!mbToken) + // String result type needs token. + return sc::FormulaResultValue(); + + if (isString(sv)) + return sc::FormulaResultValue(GetString(), IsMultiline()); + + // Invalid + return sc::FormulaResultValue(); +} + +FormulaError ScFormulaResult::GetResultError() const +{ + if (mnError != FormulaError::NONE) + return mnError; + formula::StackVar sv = GetCellResultType(); + if (sv == formula::svError) + { + if (GetType() == formula::svMatrixCell) + // don't need to test for mpToken here, GetType() already did it + return static_cast<const ScMatrixCellResultToken*>(mpToken)-> + GetUpperLeftToken()->GetError(); + if (mpToken) + return mpToken->GetError(); + } + return FormulaError::NONE; +} + +void ScFormulaResult::SetResultError( FormulaError nErr ) +{ + mnError = nErr; + if (mnError != FormulaError::NONE) + mbValueCached = false; +} + +formula::FormulaConstTokenRef ScFormulaResult::GetToken() const +{ + if (mbToken) + return mpToken; + return nullptr; +} + +formula::FormulaConstTokenRef ScFormulaResult::GetCellResultToken() const +{ + if (GetType() == formula::svMatrixCell) + // don't need to test for mpToken here, GetType() already did it + return static_cast<const ScMatrixCellResultToken*>(mpToken)->GetUpperLeftToken(); + return GetToken(); +} + +double ScFormulaResult::GetDouble() const +{ + if (mbValueCached) + return mfValue; + + if (mbToken) + { + // Should really not be of type formula::svDouble here. + if (mpToken) + { + switch (mpToken->GetType()) + { + case formula::svHybridCell: + return mpToken->GetDouble(); + case formula::svMatrixCell: + { + const ScMatrixCellResultToken* p = + static_cast<const ScMatrixCellResultToken*>(mpToken); + if (p->GetUpperLeftType() == formula::svDouble) + return p->GetUpperLeftToken()->GetDouble(); + } + break; + default: + ; // nothing + } + } + // Note that we reach here also for the default ctor and + // formula::svUnknown from GetType(). + return 0.0; + } + if (mbEmpty) + return 0.0; + return mfValue; +} + +const svl::SharedString & ScFormulaResult::GetString() const +{ + if (mbToken && mpToken) + { + switch (mpToken->GetType()) + { + case formula::svString: + case formula::svHybridCell: + return mpToken->GetString(); + case formula::svMatrixCell: + { + const ScMatrixCellResultToken* p = + static_cast<const ScMatrixCellResultToken*>(mpToken); + if (p->GetUpperLeftType() == formula::svString) + return p->GetUpperLeftToken()->GetString(); + } + break; + default: + ; // nothing + } + } + return svl::SharedString::getEmptyString(); +} + +ScConstMatrixRef ScFormulaResult::GetMatrix() const +{ + if (GetType() == formula::svMatrixCell) + return mpToken->GetMatrix(); + return nullptr; +} + +OUString ScFormulaResult::GetHybridFormula() const +{ + if (GetType() == formula::svHybridCell) + { + const ScHybridCellToken* p = static_cast<const ScHybridCellToken*>(mpToken); + return p->GetFormula(); + } + return OUString(); +} + +void ScFormulaResult::SetHybridDouble( double f ) +{ + ResetToDefaults(); + if (mbToken && mpToken) + { + if(GetType() == formula::svMatrixCell) + SetDouble(f); + else + { + svl::SharedString aString = GetString(); + OUString aFormula( GetHybridFormula()); + mpToken->DecRef(); + mpToken = new ScHybridCellToken( f, aString, aFormula, false); + mpToken->IncRef(); + } + } + else + { + mfValue = f; + mbToken = false; + meMultiline = MULTILINE_FALSE; + mbValueCached = true; + } +} + +void ScFormulaResult::SetHybridString( const svl::SharedString& rStr ) +{ + // Obtain values before changing anything. + double f = GetDouble(); + OUString aFormula( GetHybridFormula()); + ResetToDefaults(); + if (mbToken && mpToken) + mpToken->DecRef(); + mpToken = new ScHybridCellToken( f, rStr, aFormula, false); + mpToken->IncRef(); + mbToken = true; +} + +void ScFormulaResult::SetHybridEmptyDisplayedAsString() +{ + // Obtain values before changing anything. + double f = GetDouble(); + OUString aFormula( GetHybridFormula()); + svl::SharedString aStr = GetString(); + ResetToDefaults(); + if (mbToken && mpToken) + mpToken->DecRef(); + // XXX NOTE: we can't use mbEmpty and mbEmptyDisplayedAsString here because + // GetType() intentionally returns svEmptyCell if mbEmpty==true. So stick + // it into the ScHybridCellToken. + mpToken = new ScHybridCellToken( f, aStr, aFormula, true); + mpToken->IncRef(); + mbToken = true; +} + +void ScFormulaResult::SetHybridFormula( const OUString & rFormula ) +{ + // Obtain values before changing anything. + double f = GetDouble(); + svl::SharedString aStr = GetString(); + ResetToDefaults(); + if (mbToken && mpToken) + mpToken->DecRef(); + mpToken = new ScHybridCellToken( f, aStr, rFormula, false); + mpToken->IncRef(); + mbToken = true; +} + +void ScFormulaResult::SetMatrix( SCCOL nCols, SCROW nRows, const ScConstMatrixRef& pMat, const formula::FormulaToken* pUL ) +{ + ResetToDefaults(); + if (mbToken && mpToken) + mpToken->DecRef(); + mpToken = new ScMatrixFormulaCellToken(nCols, nRows, pMat, pUL); + mpToken->IncRef(); + mbToken = true; +} + +const ScMatrixFormulaCellToken* ScFormulaResult::GetMatrixFormulaCellToken() const +{ + return (GetType() == formula::svMatrixCell ? + static_cast<const ScMatrixFormulaCellToken*>(mpToken) : nullptr); +} + +ScMatrixFormulaCellToken* ScFormulaResult::GetMatrixFormulaCellTokenNonConst() +{ + return const_cast<ScMatrixFormulaCellToken*>( GetMatrixFormulaCellToken()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/grouparealistener.cxx b/sc/source/core/tool/grouparealistener.cxx new file mode 100644 index 0000000000..67862cd4a5 --- /dev/null +++ b/sc/source/core/tool/grouparealistener.cxx @@ -0,0 +1,352 @@ +/* -*- 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/. + */ + +#include <grouparealistener.hxx> +#include <brdcst.hxx> +#include <formulacell.hxx> +#include <bulkdatahint.hxx> +#include <columnspanset.hxx> +#include <column.hxx> +#include <listenerquery.hxx> +#include <listenerqueryids.hxx> +#include <document.hxx> +#include <table.hxx> + +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> + +namespace sc { + +namespace { + +class Notifier +{ + const SfxHint& mrHint; +public: + explicit Notifier( const SfxHint& rHint ) : mrHint(rHint) {} + + void operator() ( ScFormulaCell* pCell ) + { + pCell->Notify(mrHint); + } +}; + +class CollectCellAction : public sc::ColumnSpanSet::ColumnAction +{ + const FormulaGroupAreaListener& mrAreaListener; + ScAddress maPos; + std::vector<ScFormulaCell*> maCells; + +public: + explicit CollectCellAction( const FormulaGroupAreaListener& rAreaListener ) : + mrAreaListener(rAreaListener) {} + + virtual void startColumn( ScColumn* pCol ) override + { + maPos.SetTab(pCol->GetTab()); + maPos.SetCol(pCol->GetCol()); + } + + virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) override + { + if (!bVal) + return; + + mrAreaListener.collectFormulaCells(maPos.Tab(), maPos.Col(), nRow1, nRow2, maCells); + }; + + void swapCells( std::vector<ScFormulaCell*>& rCells ) + { + // Remove duplicate before the swap. Take care to sort them by tab,col,row before sorting by pointer, + // as many calc algorithms perform better if cells are processed in this order. + std::sort(maCells.begin(), maCells.end(), [](const ScFormulaCell* cell1, const ScFormulaCell* cell2) + { + if( cell1->aPos != cell2->aPos ) + return cell1->aPos < cell2->aPos; + return cell1 < cell2; + }); + std::vector<ScFormulaCell*>::iterator it = std::unique(maCells.begin(), maCells.end()); + maCells.erase(it, maCells.end()); + + rCells.swap(maCells); + } +}; + +} + +FormulaGroupAreaListener::FormulaGroupAreaListener( const ScRange& rRange, const ScDocument& rDocument, + const ScAddress& rTopCellPos, SCROW nGroupLen, bool bStartFixed, bool bEndFixed ) : + maRange(rRange), + mrDocument(rDocument), + mpColumn(nullptr), + mnTopCellRow(rTopCellPos.Row()), + mnGroupLen(nGroupLen), + mbStartFixed(bStartFixed), + mbEndFixed(bEndFixed) +{ + const ScTable* pTab = rDocument.FetchTable( rTopCellPos.Tab()); + assert(pTab); + mpColumn = pTab->FetchColumn( rTopCellPos.Col()); + assert(mpColumn); + SAL_INFO( "sc.core.grouparealistener", + "FormulaGroupAreaListener ctor this " << this << + " range " << (maRange == BCA_LISTEN_ALWAYS ? "LISTEN-ALWAYS" : maRange.Format(mrDocument, ScRefFlags::VALID)) << + " mnTopCellRow " << mnTopCellRow << " length " << mnGroupLen << + ", col/tab " << mpColumn->GetCol() << "/" << mpColumn->GetTab()); +} + +FormulaGroupAreaListener::~FormulaGroupAreaListener() +{ + SAL_INFO( "sc.core.grouparealistener", + "FormulaGroupAreaListener dtor this " << this); +} + +ScRange FormulaGroupAreaListener::getListeningRange() const +{ + ScRange aRet = maRange; + if (!mbEndFixed) + aRet.aEnd.IncRow(mnGroupLen-1); + return aRet; +} + +void FormulaGroupAreaListener::Notify( const SfxHint& rHint ) +{ + // BulkDataHint may include (SfxHintId::ScDataChanged | + // SfxHintId::ScTableOpDirty) so has to be checked first. + if ( const BulkDataHint* pBulkHint = dynamic_cast<const BulkDataHint*>(&rHint) ) + { + notifyBulkChange(*pBulkHint); + } + else if (rHint.GetId() == SfxHintId::ScDataChanged || rHint.GetId() == SfxHintId::ScTableOpDirty) + { + const ScHint& rScHint = static_cast<const ScHint&>(rHint); + notifyCellChange(rHint, rScHint.GetStartAddress(), rScHint.GetRowCount()); + } +} + +void FormulaGroupAreaListener::Query( QueryBase& rQuery ) const +{ + switch (rQuery.getId()) + { + case SC_LISTENER_QUERY_FORMULA_GROUP_RANGE: + { + const ScFormulaCell* pTop = getTopCell(); + ScRange aRange(pTop->aPos); + aRange.aEnd.IncRow(mnGroupLen-1); + QueryRange& rQR = static_cast<QueryRange&>(rQuery); + rQR.add(aRange); + } + break; + default: + ; + } +} + +void FormulaGroupAreaListener::notifyBulkChange( const BulkDataHint& rHint ) +{ + const ColumnSpanSet* pSpans = rHint.getSpans(); + if (!pSpans) + return; + + ScDocument& rDoc = const_cast<BulkDataHint&>(rHint).getDoc(); + + CollectCellAction aAction(*this); + pSpans->executeColumnAction(rDoc, aAction); + + std::vector<ScFormulaCell*> aCells; + aAction.swapCells(aCells); + ScHint aHint(SfxHintId::ScDataChanged, ScAddress()); + std::for_each(aCells.begin(), aCells.end(), Notifier(aHint)); +} + +void FormulaGroupAreaListener::collectFormulaCells( + SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2, std::vector<ScFormulaCell*>& rCells ) const +{ + PutInOrder(nRow1, nRow2); + + if (nTab < maRange.aStart.Tab() || maRange.aEnd.Tab() < nTab) + // Wrong sheet. + return; + + if (nCol < maRange.aStart.Col() || maRange.aEnd.Col() < nCol) + // Outside the column range. + return; + + collectFormulaCells(nRow1, nRow2, rCells); +} + +void FormulaGroupAreaListener::collectFormulaCells( + SCROW nRow1, SCROW nRow2, std::vector<ScFormulaCell*>& rCells ) const +{ + SAL_INFO( "sc.core.grouparealistener", + "FormulaGroupAreaListener::collectFormulaCells() this " << this << + " range " << (maRange == BCA_LISTEN_ALWAYS ? "LISTEN-ALWAYS" : maRange.Format(mrDocument, ScRefFlags::VALID)) << + " mnTopCellRow " << mnTopCellRow << " length " << mnGroupLen << + ", col/tab " << mpColumn->GetCol() << "/" << mpColumn->GetTab()); + + size_t nBlockSize = 0; + ScFormulaCell* const * pp = mpColumn->GetFormulaCellBlockAddress( mnTopCellRow, nBlockSize); + if (!pp) + { + SAL_WARN("sc.core", "GetFormulaCellBlockAddress not found"); + return; + } + + /* FIXME: this is tdf#90717, when deleting a row fixed size area listeners + * such as BCA_ALWAYS or entire row listeners are (rightly) not destroyed, + * but mnTopCellRow and mnGroupLen also not updated, which needs fixing. + * Until then pull things as straight as possible here in such situation + * and prevent crash. */ + if (!(*pp)->IsSharedTop()) + { + SCROW nRow = (*pp)->GetSharedTopRow(); + if (nRow < 0) + SAL_WARN("sc.core", "FormulaGroupAreaListener::collectFormulaCells() no shared top"); + else + { + SAL_WARN("sc.core","FormulaGroupAreaListener::collectFormulaCells() syncing mnTopCellRow from " << + mnTopCellRow << " to " << nRow); + const_cast<FormulaGroupAreaListener*>(this)->mnTopCellRow = nRow; + pp = mpColumn->GetFormulaCellBlockAddress( mnTopCellRow, nBlockSize); + if (!pp) + { + SAL_WARN("sc.core", "GetFormulaCellBlockAddress not found"); + return; + } + } + } + SCROW nLen = (*pp)->GetSharedLength(); + if (nLen != mnGroupLen) + { + SAL_WARN("sc.core", "FormulaGroupAreaListener::collectFormulaCells() syncing mnGroupLen from " << + mnGroupLen << " to " << nLen); + const_cast<FormulaGroupAreaListener*>(this)->mnGroupLen = nLen; + } + + /* With tdf#89957 it happened that the actual block size in column + * AP (shifted from AO) of sheet 'w' was smaller than the remembered group + * length and correct. This is just a very ugly workaround, the real cause + * is yet unknown, but at least don't crash in such case. The intermediate + * cause is that not all affected group area listeners are destroyed and + * newly created, so mpColumn still points to the old column that then has + * the content of a shifted column. Effectively this workaround has the + * consequence that the group area listener is fouled up and not all + * formula cells are notified... */ + if (nBlockSize < o3tl::make_unsigned(mnGroupLen)) + { + SAL_WARN("sc.core","FormulaGroupAreaListener::collectFormulaCells() nBlockSize " << + nBlockSize << " < " << mnGroupLen << " mnGroupLen"); + const_cast<FormulaGroupAreaListener*>(this)->mnGroupLen = static_cast<SCROW>(nBlockSize); + + // erAck: 2016-11-09T18:30+01:00 XXX This doesn't occur anymore, at + // least not in the original bug scenario (insert a column before H on + // sheet w) of tdf#89957 with + // http://bugs.documentfoundation.org/attachment.cgi?id=114042 + // Apparently this was fixed in the meantime, let's assume and get the + // assert bat out to hit us if it wasn't. + assert(!"something is still messing up the formula group and block size length"); + } + + ScFormulaCell* const * ppEnd = pp + mnGroupLen; + + if (mbStartFixed) + { + if (mbEndFixed) + { + // Both top and bottom row positions are absolute. Use the original range as-is. + SCROW nRefRow1 = maRange.aStart.Row(); + SCROW nRefRow2 = maRange.aEnd.Row(); + if (nRow2 < nRefRow1 || nRefRow2 < nRow1) + return; + + rCells.insert(rCells.end(), pp, ppEnd); + } + else + { + // Only the end row is relative. + SCROW nRefRow1 = maRange.aStart.Row(); + SCROW nRefRow2 = maRange.aEnd.Row(); + SCROW nMaxRefRow = nRefRow2 + mnGroupLen - 1; + if (nRow2 < nRefRow1 || nMaxRefRow < nRow1) + return; + + if (nRefRow2 < nRow1) + { + // Skip ahead to the first hit. + SCROW nSkip = nRow1 - nRefRow2; + pp += nSkip; + nRefRow2 += nSkip; + } + + assert(nRow1 <= nRefRow2); + + // Notify the first hit cell and all subsequent ones. + rCells.insert(rCells.end(), pp, ppEnd); + } + } + else if (mbEndFixed) + { + // Only the start row is relative. + SCROW nRefRow1 = maRange.aStart.Row(); + SCROW nRefRow2 = maRange.aEnd.Row(); + + if (nRow2 < nRefRow1 || nRefRow2 < nRow1) + return; + + for (; pp != ppEnd && nRefRow1 <= nRefRow2; ++pp, ++nRefRow1) + rCells.push_back(*pp); + } + else + { + // Both top and bottom row positions are relative. + SCROW nRefRow1 = maRange.aStart.Row(); + SCROW nRefRow2 = maRange.aEnd.Row(); + SCROW nMaxRefRow = nRefRow2 + mnGroupLen - 1; + if (nMaxRefRow < nRow1) + return; + + if (nRefRow2 < nRow1) + { + // The ref row range is above the changed row span. Skip ahead. + SCROW nSkip = nRow1 - nRefRow2; + pp += nSkip; + nRefRow1 += nSkip; + nRefRow2 += nSkip; + } + + // At this point the initial ref row range should be overlapping the + // dirty cell range. + assert(nRow1 <= nRefRow2); + + // Keep sliding down until the top ref row position is below the + // bottom row of the dirty cell range. + for (; pp != ppEnd && nRefRow1 <= nRow2; ++pp, ++nRefRow1, ++nRefRow2) + rCells.push_back(*pp); + } +} + +const ScFormulaCell* FormulaGroupAreaListener::getTopCell() const +{ + size_t nBlockSize = 0; + const ScFormulaCell* const * pp = mpColumn->GetFormulaCellBlockAddress( mnTopCellRow, nBlockSize); + SAL_WARN_IF(!pp, "sc.core.grouparealistener", "GetFormulaCellBlockAddress not found"); + return pp ? *pp : nullptr; +} + +void FormulaGroupAreaListener::notifyCellChange( const SfxHint& rHint, const ScAddress& rPos, SCROW nNumRows ) +{ + // Determine which formula cells within the group need to be notified of this change. + std::vector<ScFormulaCell*> aCells; + collectFormulaCells(rPos.Tab(), rPos.Col(), rPos.Row(), rPos.Row() + (nNumRows - 1), aCells); + std::for_each(aCells.begin(), aCells.end(), Notifier(rHint)); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/hints.cxx b/sc/source/core/tool/hints.cxx new file mode 100644 index 0000000000..06ac946d9b --- /dev/null +++ b/sc/source/core/tool/hints.cxx @@ -0,0 +1,114 @@ +/* -*- 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 <hints.hxx> +#include <utility> + +// ScPaintHint - info what has to be repainted + +ScPaintHint::ScPaintHint( const ScRange& rRng, PaintPartFlags nPaint ) : + aRange( rRng ), + nParts( nPaint ) +{ +} + +ScPaintHint::~ScPaintHint() +{ +} + +// ScUpdateRefHint - update references + +ScUpdateRefHint::ScUpdateRefHint( UpdateRefMode eMode, const ScRange& rR, + SCCOL nX, SCROW nY, SCTAB nZ ) : + eUpdateRefMode( eMode ), + aRange( rR ), + nDx( nX ), + nDy( nY ), + nDz( nZ ) +{ +} + +ScUpdateRefHint::~ScUpdateRefHint() +{ +} + +// ScLinkRefreshedHint - a link has been refreshed + +ScLinkRefreshedHint::ScLinkRefreshedHint() : + nLinkType( ScLinkRefType::NONE ) +{ +} + +ScLinkRefreshedHint::~ScLinkRefreshedHint() +{ +} + +void ScLinkRefreshedHint::SetSheetLink( const OUString& rSourceUrl ) +{ + nLinkType = ScLinkRefType::SHEET; + aUrl = rSourceUrl; +} + +void ScLinkRefreshedHint::SetDdeLink( + const OUString& rA, const OUString& rT, const OUString& rI ) +{ + nLinkType = ScLinkRefType::DDE; + aDdeAppl = rA; + aDdeTopic = rT; + aDdeItem = rI; +} + +void ScLinkRefreshedHint::SetAreaLink( const ScAddress& rPos ) +{ + nLinkType = ScLinkRefType::AREA; + aDestPos = rPos; +} + +// ScAutoStyleHint - STYLE() function has been called + +ScAutoStyleHint::ScAutoStyleHint( const ScRange& rR, OUString aSt1, + sal_uLong nT, OUString aSt2 ) : + aRange( rR ), + aStyle1(std::move( aSt1 )), + aStyle2(std::move( aSt2 )), + nTimeout( nT ) +{ +} + +ScAutoStyleHint::~ScAutoStyleHint() +{ +} + +ScDBRangeRefreshedHint::ScDBRangeRefreshedHint( const ScImportParam& rP ) + : aParam(rP) +{ +} +ScDBRangeRefreshedHint::~ScDBRangeRefreshedHint() +{ +} + +ScDataPilotModifiedHint::ScDataPilotModifiedHint( OUString aName ) + : maName(std::move(aName)) +{ +} +ScDataPilotModifiedHint::~ScDataPilotModifiedHint() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/inputopt.cxx b/sc/source/core/tool/inputopt.cxx new file mode 100644 index 0000000000..13781040ee --- /dev/null +++ b/sc/source/core/tool/inputopt.cxx @@ -0,0 +1,157 @@ +/* -*- 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 <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <osl/diagnose.h> + +#include <inputopt.hxx> +#include <global.hxx> + +using namespace utl; +using namespace com::sun::star::uno; + +// ScInputOptions - input options + +ScInputOptions::ScInputOptions() + : nMoveDir(DIR_BOTTOM) + , bMoveSelection(true) + , bEnterEdit(false) + , bExtendFormat(false) + , bRangeFinder(true) + , bExpandRefs(false) + , mbSortRefUpdate(true) + , bMarkHeader(true) + , bUseTabCol(false) + , bTextWysiwyg(false) + , bReplCellsWarn(true) + , bLegacyCellSelection(false) + , bEnterPasteMode(false) +{ +} + +// Config Item containing input options + +constexpr OUStringLiteral CFGPATH_INPUT = u"Office.Calc/Input"; + +#define SCINPUTOPT_MOVEDIR 0 +#define SCINPUTOPT_MOVESEL 1 +#define SCINPUTOPT_EDTEREDIT 2 +#define SCINPUTOPT_EXTENDFMT 3 +#define SCINPUTOPT_RANGEFIND 4 +#define SCINPUTOPT_EXPANDREFS 5 +#define SCINPUTOPT_SORT_REF_UPDATE 6 +#define SCINPUTOPT_MARKHEADER 7 +#define SCINPUTOPT_USETABCOL 8 +#define SCINPUTOPT_REPLCELLSWARN 9 +#define SCINPUTOPT_LEGACY_CELL_SELECTION 10 +#define SCINPUTOPT_ENTER_PASTE_MODE 11 + +Sequence<OUString> ScInputCfg::GetPropertyNames() +{ + return {"MoveSelectionDirection", // SCINPUTOPT_MOVEDIR + "MoveSelection", // SCINPUTOPT_MOVESEL + "SwitchToEditMode", // SCINPUTOPT_EDTEREDIT + "ExpandFormatting", // SCINPUTOPT_EXTENDFMT + "ShowReference", // SCINPUTOPT_RANGEFIND + "ExpandReference", // SCINPUTOPT_EXPANDREFS + "UpdateReferenceOnSort", // SCINPUTOPT_SORT_REF_UPDATE + "HighlightSelection", // SCINPUTOPT_MARKHEADER + "UseTabCol", // SCINPUTOPT_USETABCOL + "ReplaceCellsWarning", // SCINPUTOPT_REPLCELLSWARN + "LegacyCellSelection", // SCINPUTOPT_LEGACY_CELL_SELECTION + "EnterPasteMode"}; // SCINPUTOPT_ENTER_PASTE_MODE +} + +ScInputCfg::ScInputCfg() : + ConfigItem( CFGPATH_INPUT ) +{ + Sequence<OUString> aNames = GetPropertyNames(); + EnableNotification(aNames); + ReadCfg(); +} + +void ScInputCfg::ReadCfg() +{ + const Sequence<OUString> aNames = GetPropertyNames(); + const Sequence<Any> aValues = GetProperties(aNames); + OSL_ENSURE(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if(aValues.getLength() != aNames.getLength()) + return; + + if (sal_Int32 nVal; aValues[SCINPUTOPT_MOVEDIR] >>= nVal) + SetMoveDir(static_cast<sal_uInt16>(nVal)); + if (bool bVal; aValues[SCINPUTOPT_MOVESEL] >>= bVal) + SetMoveSelection(bVal); + if (bool bVal; aValues[SCINPUTOPT_EDTEREDIT] >>= bVal) + SetEnterEdit(bVal); + if (bool bVal; aValues[SCINPUTOPT_EXTENDFMT] >>= bVal) + SetExtendFormat(bVal); + if (bool bVal; aValues[SCINPUTOPT_RANGEFIND] >>= bVal) + SetRangeFinder(bVal); + if (bool bVal; aValues[SCINPUTOPT_EXPANDREFS] >>= bVal) + SetExpandRefs(bVal); + if (bool bVal; aValues[SCINPUTOPT_SORT_REF_UPDATE] >>= bVal) + SetSortRefUpdate(bVal); + if (bool bVal; aValues[SCINPUTOPT_MARKHEADER] >>= bVal) + SetMarkHeader(bVal); + if (bool bVal; aValues[SCINPUTOPT_USETABCOL] >>= bVal) + SetUseTabCol(bVal); + if (bool bVal; aValues[SCINPUTOPT_REPLCELLSWARN] >>= bVal) + SetReplaceCellsWarn(bVal); + if (bool bVal; aValues[SCINPUTOPT_LEGACY_CELL_SELECTION] >>= bVal) + SetLegacyCellSelection(bVal); + if (bool bVal; aValues[SCINPUTOPT_ENTER_PASTE_MODE] >>= bVal) + SetEnterPasteMode(bVal); +} + +void ScInputCfg::ImplCommit() +{ + Sequence<OUString> aNames = GetPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + pValues[SCINPUTOPT_MOVEDIR] <<= static_cast<sal_Int32>(GetMoveDir()); + pValues[SCINPUTOPT_MOVESEL] <<= GetMoveSelection(); + pValues[SCINPUTOPT_EDTEREDIT] <<= GetEnterEdit(); + pValues[SCINPUTOPT_EXTENDFMT] <<= GetExtendFormat(); + pValues[SCINPUTOPT_RANGEFIND] <<= GetRangeFinder(); + pValues[SCINPUTOPT_EXPANDREFS] <<= GetExpandRefs(); + pValues[SCINPUTOPT_SORT_REF_UPDATE] <<= GetSortRefUpdate(); + pValues[SCINPUTOPT_MARKHEADER] <<= GetMarkHeader(); + pValues[SCINPUTOPT_USETABCOL] <<= GetUseTabCol(); + pValues[SCINPUTOPT_REPLCELLSWARN] <<= GetReplaceCellsWarn(); + pValues[SCINPUTOPT_LEGACY_CELL_SELECTION] <<= GetLegacyCellSelection(); + pValues[SCINPUTOPT_ENTER_PASTE_MODE] <<= GetEnterPasteMode(); + PutProperties(aNames, aValues); +} + +void ScInputCfg::Notify( const Sequence<OUString>& /* aPropertyNames */ ) +{ + ReadCfg(); +} + +void ScInputCfg::SetOptions( const ScInputOptions& rNew ) +{ + *static_cast<ScInputOptions*>(this) = rNew; + SetModified(); + Commit(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/interpr1.cxx b/sc/source/core/tool/interpr1.cxx new file mode 100644 index 0000000000..e372228721 --- /dev/null +++ b/sc/source/core/tool/interpr1.cxx @@ -0,0 +1,10332 @@ +/* -*- 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 <interpre.hxx> + +#include <scitems.hxx> +#include <editeng/langitem.hxx> +#include <editeng/justifyitem.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/temporary.hxx> +#include <osl/thread.h> +#include <unotools/textsearch.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <svl/zformat.hxx> +#include <tools/urlobj.hxx> +#include <unotools/charclass.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/printer.hxx> +#include <unotools/collatorwrapper.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <rtl/character.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <unicode/uchar.h> +#include <unicode/regex.h> +#include <i18nlangtag/mslangid.hxx> + +#include <patattr.hxx> +#include <global.hxx> +#include <document.hxx> +#include <dociter.hxx> +#include <docsh.hxx> +#include <formulacell.hxx> +#include <scmatrix.hxx> +#include <docoptio.hxx> +#include <attrib.hxx> +#include <jumpmatrix.hxx> +#include <cellkeytranslator.hxx> +#include <lookupcache.hxx> +#include <rangenam.hxx> +#include <rangeutl.hxx> +#include <compiler.hxx> +#include <externalrefmgr.hxx> +#include <doubleref.hxx> +#include <queryparam.hxx> +#include <queryentry.hxx> +#include <queryiter.hxx> +#include <tokenarray.hxx> +#include <compare.hxx> + +#include <comphelper/processfactory.hxx> +#include <comphelper/random.hxx> +#include <comphelper/string.hxx> +#include <svl/sharedstringpool.hxx> + +#include <stdlib.h> +#include <vector> +#include <memory> +#include <limits> +#include <string_view> +#include <cmath> + +const sal_uInt64 n2power48 = SAL_CONST_UINT64( 281474976710656); // 2^48 + +ScCalcConfig *ScInterpreter::mpGlobalConfig = nullptr; + +using namespace formula; +using ::std::unique_ptr; + +void ScInterpreter::ScIfJump() +{ + const short* pJump = pCur->GetJump(); + short nJumpCount = pJump[ 0 ]; + MatrixJumpConditionToMatrix(); + switch ( GetStackType() ) + { + case svMatrix: + { + ScMatrixRef pMat = PopMatrix(); + if ( !pMat ) + PushIllegalParameter(); + else + { + FormulaConstTokenRef xNew; + ScTokenMatrixMap::const_iterator aMapIter; + // DoubleError handled by JumpMatrix + pMat->SetErrorInterpreter( nullptr); + SCSIZE nCols, nRows; + pMat->GetDimensions( nCols, nRows ); + if ( nCols == 0 || nRows == 0 ) + { + PushIllegalArgument(); + return; + } + else if ((aMapIter = maTokenMatrixMap.find( pCur)) != maTokenMatrixMap.end()) + xNew = (*aMapIter).second; + else + { + std::shared_ptr<ScJumpMatrix> pJumpMat( std::make_shared<ScJumpMatrix>( + pCur->GetOpCode(), nCols, nRows)); + for ( SCSIZE nC=0; nC < nCols; ++nC ) + { + for ( SCSIZE nR=0; nR < nRows; ++nR ) + { + double fVal; + bool bTrue; + bool bIsValue = pMat->IsValue(nC, nR); + if (bIsValue) + { + fVal = pMat->GetDouble(nC, nR); + bIsValue = std::isfinite(fVal); + bTrue = bIsValue && (fVal != 0.0); + if (bTrue) + fVal = 1.0; + } + else + { + // Treat empty and empty path as 0, but string + // as error. ScMatrix::IsValueOrEmpty() returns + // true for any empty, empty path, empty cell, + // empty result. + bIsValue = pMat->IsValueOrEmpty(nC, nR); + bTrue = false; + fVal = (bIsValue ? 0.0 : CreateDoubleError( FormulaError::NoValue)); + } + if ( bTrue ) + { // TRUE + if( nJumpCount >= 2 ) + { // THEN path + pJumpMat->SetJump( nC, nR, fVal, + pJump[ 1 ], + pJump[ nJumpCount ]); + } + else + { // no parameter given for THEN + pJumpMat->SetJump( nC, nR, fVal, + pJump[ nJumpCount ], + pJump[ nJumpCount ]); + } + } + else + { // FALSE + if( nJumpCount == 3 && bIsValue ) + { // ELSE path + pJumpMat->SetJump( nC, nR, fVal, + pJump[ 2 ], + pJump[ nJumpCount ]); + } + else + { // no parameter given for ELSE, + // or DoubleError + pJumpMat->SetJump( nC, nR, fVal, + pJump[ nJumpCount ], + pJump[ nJumpCount ]); + } + } + } + } + xNew = new ScJumpMatrixToken( pJumpMat ); + GetTokenMatrixMap().emplace(pCur, xNew); + } + if (!xNew) + { + PushIllegalArgument(); + return; + } + PushTokenRef( xNew); + // set endpoint of path for main code line + aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] ); + } + } + break; + default: + { + const bool bCondition = GetBool(); + if (nGlobalError != FormulaError::NONE) + { // Propagate error, not THEN- or ELSE-path, jump behind. + PushError(nGlobalError); + aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] ); + } + else if ( bCondition ) + { // TRUE + if( nJumpCount >= 2 ) + { // THEN path + aCode.Jump( pJump[ 1 ], pJump[ nJumpCount ] ); + } + else + { // no parameter given for THEN + nFuncFmtType = SvNumFormatType::LOGICAL; + PushInt(1); + aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] ); + } + } + else + { // FALSE + if( nJumpCount == 3 ) + { // ELSE path + aCode.Jump( pJump[ 2 ], pJump[ nJumpCount ] ); + } + else + { // no parameter given for ELSE + nFuncFmtType = SvNumFormatType::LOGICAL; + PushInt(0); + aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] ); + } + } + } + } +} + +/** Store a matrix value in another matrix in the context of that other matrix + is the result matrix of a jump matrix. All arguments must be valid and are + not checked. */ +static void lcl_storeJumpMatResult( + const ScMatrix* pMat, ScJumpMatrix* pJumpMat, SCSIZE nC, SCSIZE nR ) +{ + if ( pMat->IsValue( nC, nR ) ) + { + double fVal = pMat->GetDouble( nC, nR ); + pJumpMat->PutResultDouble( fVal, nC, nR ); + } + else if ( pMat->IsEmpty( nC, nR ) ) + { + pJumpMat->PutResultEmpty( nC, nR ); + } + else + { + pJumpMat->PutResultString(pMat->GetString(nC, nR), nC, nR); + } +} + +void ScInterpreter::ScIfError( bool bNAonly ) +{ + const short* pJump = pCur->GetJump(); + short nJumpCount = pJump[ 0 ]; + if (!sp || nJumpCount != 2) + { + // Reset nGlobalError here to not propagate the old error, if any. + nGlobalError = (sp ? FormulaError::ParameterExpected : FormulaError::UnknownStackVariable); + PushError( nGlobalError); + aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] ); + return; + } + + FormulaConstTokenRef xToken( pStack[ sp - 1 ] ); + bool bError = false; + FormulaError nOldGlobalError = nGlobalError; + nGlobalError = FormulaError::NONE; + + MatrixJumpConditionToMatrix(); + switch (GetStackType()) + { + default: + Pop(); + // Act on implicitly propagated error, if any. + if (nOldGlobalError != FormulaError::NONE) + nGlobalError = nOldGlobalError; + if (nGlobalError != FormulaError::NONE) + bError = true; + break; + case svError: + PopError(); + bError = true; + break; + case svDoubleRef: + case svSingleRef: + { + ScAddress aAdr; + if (!PopDoubleRefOrSingleRef( aAdr)) + bError = true; + else + { + + ScRefCellValue aCell(mrDoc, aAdr); + nGlobalError = GetCellErrCode(aCell); + if (nGlobalError != FormulaError::NONE) + bError = true; + } + } + break; + case svExternalSingleRef: + case svExternalDoubleRef: + { + double fVal; + svl::SharedString aStr; + // Handles also existing jump matrix case and sets error on + // elements. + GetDoubleOrStringFromMatrix( fVal, aStr); + if (nGlobalError != FormulaError::NONE) + bError = true; + } + break; + case svMatrix: + { + const ScMatrixRef pMat = PopMatrix(); + if (!pMat || (nGlobalError != FormulaError::NONE && (!bNAonly || nGlobalError == FormulaError::NotAvailable))) + { + bError = true; + break; // switch + } + // If the matrix has no queried error at all we can simply use + // it as result and don't need to bother with jump matrix. + SCSIZE nErrorCol = ::std::numeric_limits<SCSIZE>::max(), + nErrorRow = ::std::numeric_limits<SCSIZE>::max(); + SCSIZE nCols, nRows; + pMat->GetDimensions( nCols, nRows ); + if (nCols == 0 || nRows == 0) + { + bError = true; + break; // switch + } + for (SCSIZE nC=0; nC < nCols && !bError; ++nC) + { + for (SCSIZE nR=0; nR < nRows && !bError; ++nR) + { + FormulaError nErr = pMat->GetError( nC, nR ); + if (nErr != FormulaError::NONE && (!bNAonly || nErr == FormulaError::NotAvailable)) + { + bError = true; + nErrorCol = nC; + nErrorRow = nR; + } + } + } + if (!bError) + break; // switch, we're done and have the result + + FormulaConstTokenRef xNew; + ScTokenMatrixMap::const_iterator aMapIter; + if ((aMapIter = maTokenMatrixMap.find( pCur)) != maTokenMatrixMap.end()) + { + xNew = (*aMapIter).second; + } + else + { + const ScMatrix* pMatPtr = pMat.get(); + std::shared_ptr<ScJumpMatrix> pJumpMat( std::make_shared<ScJumpMatrix>( + pCur->GetOpCode(), nCols, nRows)); + // Init all jumps to no error to save single calls. Error + // is the exceptional condition. + const double fFlagResult = CreateDoubleError( FormulaError::JumpMatHasResult); + pJumpMat->SetAllJumps( fFlagResult, pJump[ nJumpCount ], pJump[ nJumpCount ] ); + // Up to first error position simply store results, no need + // to evaluate error conditions again. + SCSIZE nC = 0, nR = 0; + for ( ; nC < nCols && (nC != nErrorCol || nR != nErrorRow); /*nop*/ ) + { + for (nR = 0 ; nR < nRows && (nC != nErrorCol || nR != nErrorRow); ++nR) + { + lcl_storeJumpMatResult(pMatPtr, pJumpMat.get(), nC, nR); + } + if (nC != nErrorCol && nR != nErrorRow) + ++nC; + } + // Now the mixed cases. + for ( ; nC < nCols; ++nC) + { + for ( ; nR < nRows; ++nR) + { + FormulaError nErr = pMat->GetError( nC, nR ); + if (nErr != FormulaError::NONE && (!bNAonly || nErr == FormulaError::NotAvailable)) + { // TRUE, THEN path + pJumpMat->SetJump( nC, nR, 1.0, pJump[ 1 ], pJump[ nJumpCount ] ); + } + else + { // FALSE, EMPTY path, store result instead + lcl_storeJumpMatResult(pMatPtr, pJumpMat.get(), nC, nR); + } + } + nR = 0; + } + xNew = new ScJumpMatrixToken( pJumpMat ); + GetTokenMatrixMap().emplace( pCur, xNew ); + } + nGlobalError = nOldGlobalError; + PushTokenRef( xNew ); + // set endpoint of path for main code line + aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] ); + return; + } + break; + } + + if (bError && (!bNAonly || nGlobalError == FormulaError::NotAvailable)) + { + // error, calculate 2nd argument + nGlobalError = FormulaError::NONE; + aCode.Jump( pJump[ 1 ], pJump[ nJumpCount ] ); + } + else + { + // no error, push 1st argument and continue + nGlobalError = nOldGlobalError; + PushTokenRef( xToken); + aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] ); + } +} + +void ScInterpreter::ScChooseJump() +{ + // We have to set a jump, if there was none chosen because of an error set + // it to endpoint. + bool bHaveJump = false; + const short* pJump = pCur->GetJump(); + short nJumpCount = pJump[ 0 ]; + MatrixJumpConditionToMatrix(); + switch ( GetStackType() ) + { + case svMatrix: + { + ScMatrixRef pMat = PopMatrix(); + if ( !pMat ) + PushIllegalParameter(); + else + { + FormulaConstTokenRef xNew; + ScTokenMatrixMap::const_iterator aMapIter; + // DoubleError handled by JumpMatrix + pMat->SetErrorInterpreter( nullptr); + SCSIZE nCols, nRows; + pMat->GetDimensions( nCols, nRows ); + if ( nCols == 0 || nRows == 0 ) + PushIllegalParameter(); + else if ((aMapIter = maTokenMatrixMap.find( + pCur)) != maTokenMatrixMap.end()) + xNew = (*aMapIter).second; + else + { + std::shared_ptr<ScJumpMatrix> pJumpMat( std::make_shared<ScJumpMatrix>( + pCur->GetOpCode(), nCols, nRows)); + for ( SCSIZE nC=0; nC < nCols; ++nC ) + { + for ( SCSIZE nR=0; nR < nRows; ++nR ) + { + double fVal; + bool bIsValue = pMat->IsValue(nC, nR); + if ( bIsValue ) + { + fVal = pMat->GetDouble(nC, nR); + bIsValue = std::isfinite( fVal ); + if ( bIsValue ) + { + fVal = ::rtl::math::approxFloor( fVal); + if ( (fVal < 1) || (fVal >= nJumpCount)) + { + bIsValue = false; + fVal = CreateDoubleError( + FormulaError::IllegalArgument); + } + } + } + else + { + fVal = CreateDoubleError( FormulaError::NoValue); + } + if ( bIsValue ) + { + pJumpMat->SetJump( nC, nR, fVal, + pJump[ static_cast<short>(fVal) ], + pJump[ nJumpCount ]); + } + else + { + pJumpMat->SetJump( nC, nR, fVal, + pJump[ nJumpCount ], + pJump[ nJumpCount ]); + } + } + } + xNew = new ScJumpMatrixToken( pJumpMat ); + GetTokenMatrixMap().emplace(pCur, xNew); + } + if (xNew) + { + PushTokenRef( xNew); + // set endpoint of path for main code line + aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] ); + bHaveJump = true; + } + } + } + break; + default: + { + sal_Int16 nJumpIndex = GetInt16(); + if (nGlobalError == FormulaError::NONE && (nJumpIndex >= 1) && (nJumpIndex < nJumpCount)) + { + aCode.Jump( pJump[ static_cast<short>(nJumpIndex) ], pJump[ nJumpCount ] ); + bHaveJump = true; + } + else + PushIllegalArgument(); + } + } + if (!bHaveJump) + aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] ); +} + +static void lcl_AdjustJumpMatrix( ScJumpMatrix* pJumpM, SCSIZE nParmCols, SCSIZE nParmRows ) +{ + SCSIZE nJumpCols, nJumpRows; + SCSIZE nResCols, nResRows; + SCSIZE nAdjustCols, nAdjustRows; + pJumpM->GetDimensions( nJumpCols, nJumpRows ); + pJumpM->GetResMatDimensions( nResCols, nResRows ); + if (!(( nJumpCols == 1 && nParmCols > nResCols ) || + ( nJumpRows == 1 && nParmRows > nResRows ))) + return; + + if ( nJumpCols == 1 && nJumpRows == 1 ) + { + nAdjustCols = std::max(nParmCols, nResCols); + nAdjustRows = std::max(nParmRows, nResRows); + } + else if ( nJumpCols == 1 ) + { + nAdjustCols = nParmCols; + nAdjustRows = nResRows; + } + else + { + nAdjustCols = nResCols; + nAdjustRows = nParmRows; + } + pJumpM->SetNewResMat( nAdjustCols, nAdjustRows ); +} + +bool ScInterpreter::JumpMatrix( short nStackLevel ) +{ + pJumpMatrix = pStack[sp-nStackLevel]->GetJumpMatrix(); + bool bHasResMat = pJumpMatrix->HasResultMatrix(); + SCSIZE nC, nR; + if ( nStackLevel == 2 ) + { + if ( aCode.HasStacked() ) + aCode.Pop(); // pop what Jump() pushed + else + { + assert(!"pop goes the weasel"); + } + + if ( !bHasResMat ) + { + Pop(); + SetError( FormulaError::UnknownStackVariable ); + } + else + { + pJumpMatrix->GetPos( nC, nR ); + switch ( GetStackType() ) + { + case svDouble: + { + double fVal = GetDouble(); + if ( nGlobalError != FormulaError::NONE ) + { + fVal = CreateDoubleError( nGlobalError ); + nGlobalError = FormulaError::NONE; + } + pJumpMatrix->PutResultDouble( fVal, nC, nR ); + } + break; + case svString: + { + svl::SharedString aStr = GetString(); + if ( nGlobalError != FormulaError::NONE ) + { + pJumpMatrix->PutResultDouble( CreateDoubleError( nGlobalError), + nC, nR); + nGlobalError = FormulaError::NONE; + } + else + pJumpMatrix->PutResultString(aStr, nC, nR); + } + break; + case svSingleRef: + { + FormulaConstTokenRef xRef = pStack[sp-1]; + ScAddress aAdr; + PopSingleRef( aAdr ); + if ( nGlobalError != FormulaError::NONE ) + { + pJumpMatrix->PutResultDouble( CreateDoubleError( nGlobalError), + nC, nR); + nGlobalError = FormulaError::NONE; + } + else + { + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasEmptyValue()) + pJumpMatrix->PutResultEmpty( nC, nR ); + else if (aCell.hasNumeric()) + { + double fVal = GetCellValue(aAdr, aCell); + if ( nGlobalError != FormulaError::NONE ) + { + fVal = CreateDoubleError( + nGlobalError); + nGlobalError = FormulaError::NONE; + } + pJumpMatrix->PutResultDouble( fVal, nC, nR ); + } + else + { + svl::SharedString aStr; + GetCellString(aStr, aCell); + if ( nGlobalError != FormulaError::NONE ) + { + pJumpMatrix->PutResultDouble( CreateDoubleError( + nGlobalError), nC, nR); + nGlobalError = FormulaError::NONE; + } + else + pJumpMatrix->PutResultString(aStr, nC, nR); + } + } + + formula::ParamClass eReturnType = ScParameterClassification::GetParameterType( pCur, SAL_MAX_UINT16); + if (eReturnType == ParamClass::Reference) + { + /* TODO: What about error handling and do we actually + * need the result matrix above at all in this case? */ + ScComplexRefData aRef; + aRef.Ref1 = aRef.Ref2 = *(xRef->GetSingleRef()); + pJumpMatrix->GetRefList().push_back( aRef); + } + } + break; + case svDoubleRef: + { // upper left plus offset within matrix + FormulaConstTokenRef xRef = pStack[sp-1]; + double fVal; + ScRange aRange; + PopDoubleRef( aRange ); + if ( nGlobalError != FormulaError::NONE ) + { + fVal = CreateDoubleError( nGlobalError ); + nGlobalError = FormulaError::NONE; + pJumpMatrix->PutResultDouble( fVal, nC, nR ); + } + else + { + // Do not modify the original range because we use it + // to adjust the size of the result matrix if necessary. + ScAddress aAdr( aRange.aStart); + sal_uLong nCol = static_cast<sal_uLong>(aAdr.Col()) + nC; + sal_uLong nRow = static_cast<sal_uLong>(aAdr.Row()) + nR; + if ((nCol > o3tl::make_unsigned(aRange.aEnd.Col()) && + aRange.aEnd.Col() != aRange.aStart.Col()) + || (nRow > o3tl::make_unsigned(aRange.aEnd.Row()) && + aRange.aEnd.Row() != aRange.aStart.Row())) + { + fVal = CreateDoubleError( FormulaError::NotAvailable ); + pJumpMatrix->PutResultDouble( fVal, nC, nR ); + } + else + { + // Replicate column and/or row of a vector if it is + // one. Note that this could be a range reference + // that in fact consists of only one cell, e.g. A1:A1 + if (aRange.aEnd.Col() == aRange.aStart.Col()) + nCol = aRange.aStart.Col(); + if (aRange.aEnd.Row() == aRange.aStart.Row()) + nRow = aRange.aStart.Row(); + aAdr.SetCol( static_cast<SCCOL>(nCol) ); + aAdr.SetRow( static_cast<SCROW>(nRow) ); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasEmptyValue()) + pJumpMatrix->PutResultEmpty( nC, nR ); + else if (aCell.hasNumeric()) + { + double fCellVal = GetCellValue(aAdr, aCell); + if ( nGlobalError != FormulaError::NONE ) + { + fCellVal = CreateDoubleError( + nGlobalError); + nGlobalError = FormulaError::NONE; + } + pJumpMatrix->PutResultDouble( fCellVal, nC, nR ); + } + else + { + svl::SharedString aStr; + GetCellString(aStr, aCell); + if ( nGlobalError != FormulaError::NONE ) + { + pJumpMatrix->PutResultDouble( CreateDoubleError( + nGlobalError), nC, nR); + nGlobalError = FormulaError::NONE; + } + else + pJumpMatrix->PutResultString(aStr, nC, nR); + } + } + SCSIZE nParmCols = aRange.aEnd.Col() - aRange.aStart.Col() + 1; + SCSIZE nParmRows = aRange.aEnd.Row() - aRange.aStart.Row() + 1; + lcl_AdjustJumpMatrix( pJumpMatrix, nParmCols, nParmRows ); + } + + formula::ParamClass eReturnType = ScParameterClassification::GetParameterType( pCur, SAL_MAX_UINT16); + if (eReturnType == ParamClass::Reference) + { + /* TODO: What about error handling and do we actually + * need the result matrix above at all in this case? */ + pJumpMatrix->GetRefList().push_back( *(xRef->GetDoubleRef())); + } + } + break; + case svExternalSingleRef: + { + ScExternalRefCache::TokenRef pToken; + PopExternalSingleRef(pToken); + if (nGlobalError != FormulaError::NONE) + { + pJumpMatrix->PutResultDouble( CreateDoubleError( nGlobalError), nC, nR ); + nGlobalError = FormulaError::NONE; + } + else + { + switch (pToken->GetType()) + { + case svDouble: + pJumpMatrix->PutResultDouble( pToken->GetDouble(), nC, nR ); + break; + case svString: + pJumpMatrix->PutResultString( pToken->GetString(), nC, nR ); + break; + case svEmptyCell: + pJumpMatrix->PutResultEmpty( nC, nR ); + break; + default: + // svError was already handled (set by + // PopExternalSingleRef()) with nGlobalError + // above. + assert(!"unhandled svExternalSingleRef case"); + pJumpMatrix->PutResultDouble( CreateDoubleError( + FormulaError::UnknownStackVariable), nC, nR ); + } + } + } + break; + case svExternalDoubleRef: + case svMatrix: + { // match matrix offsets + double fVal; + ScMatrixRef pMat = GetMatrix(); + if ( nGlobalError != FormulaError::NONE ) + { + fVal = CreateDoubleError( nGlobalError ); + nGlobalError = FormulaError::NONE; + pJumpMatrix->PutResultDouble( fVal, nC, nR ); + } + else if ( !pMat ) + { + fVal = CreateDoubleError( FormulaError::UnknownVariable ); + pJumpMatrix->PutResultDouble( fVal, nC, nR ); + } + else + { + SCSIZE nCols, nRows; + pMat->GetDimensions( nCols, nRows ); + if ((nCols <= nC && nCols != 1) || + (nRows <= nR && nRows != 1)) + { + fVal = CreateDoubleError( FormulaError::NotAvailable ); + pJumpMatrix->PutResultDouble( fVal, nC, nR ); + } + else + { + // GetMatrix() does SetErrorInterpreter() at the + // matrix, do not propagate an error from + // matrix->GetValue() as global error. + pMat->SetErrorInterpreter(nullptr); + lcl_storeJumpMatResult(pMat.get(), pJumpMatrix, nC, nR); + } + lcl_AdjustJumpMatrix( pJumpMatrix, nCols, nRows ); + } + } + break; + case svError: + { + PopError(); + double fVal = CreateDoubleError( nGlobalError); + nGlobalError = FormulaError::NONE; + pJumpMatrix->PutResultDouble( fVal, nC, nR ); + } + break; + default: + { + Pop(); + double fVal = CreateDoubleError( FormulaError::IllegalArgument); + pJumpMatrix->PutResultDouble( fVal, nC, nR ); + } + } + } + } + bool bCont = pJumpMatrix->Next( nC, nR ); + if ( bCont ) + { + double fBool; + short nStart, nNext, nStop; + pJumpMatrix->GetJump( nC, nR, fBool, nStart, nNext, nStop ); + while ( bCont && nStart == nNext ) + { // push all results that have no jump path + if ( bHasResMat && (GetDoubleErrorValue( fBool) != FormulaError::JumpMatHasResult) ) + { + // a false without path results in an empty path value + if ( fBool == 0.0 ) + pJumpMatrix->PutResultEmptyPath( nC, nR ); + else + pJumpMatrix->PutResultDouble( fBool, nC, nR ); + } + bCont = pJumpMatrix->Next( nC, nR ); + if ( bCont ) + pJumpMatrix->GetJump( nC, nR, fBool, nStart, nNext, nStop ); + } + if ( bCont && nStart != nNext ) + { + const ScTokenVec & rParams = pJumpMatrix->GetJumpParameters(); + for ( auto const & i : rParams ) + { + // This is not the current state of the interpreter, so + // push without error, and elements' errors are coded into + // double. + PushWithoutError(*i); + } + aCode.Jump( nStart, nNext, nStop ); + } + } + if ( !bCont ) + { // We're done with it, throw away jump matrix, keep result. + // For an intermediate result of Reference use the array of references + // if there are more than one reference and the current ForceArray + // context is ReferenceOrRefArray. + // Else (also for a final result of Reference) use the matrix. + // Treat the result of a jump command as final and use the matrix (see + // tdf#115493 for why). + if (pCur->GetInForceArray() == ParamClass::ReferenceOrRefArray && + pJumpMatrix->GetRefList().size() > 1 && + ScParameterClassification::GetParameterType( pCur, SAL_MAX_UINT16) == ParamClass::Reference && + !FormulaCompiler::IsOpCodeJumpCommand( pJumpMatrix->GetOpCode()) && + aCode.PeekNextOperator()) + { + FormulaTokenRef xRef = new ScRefListToken(true); + *(xRef->GetRefList()) = pJumpMatrix->GetRefList(); + pJumpMatrix = nullptr; + Pop(); + PushTokenRef( xRef); + maTokenMatrixMap.erase( pCur); + // There's no result matrix to remember in this case. + } + else + { + ScMatrix* pResMat = pJumpMatrix->GetResultMatrix(); + pJumpMatrix = nullptr; + Pop(); + PushMatrix( pResMat ); + // Remove jump matrix from map and remember result matrix in case it + // could be reused in another path of the same condition. + maTokenMatrixMap.erase( pCur); + maTokenMatrixMap.emplace(pCur, pStack[sp-1]); + } + return true; + } + return false; +} + +double ScInterpreter::Compare( ScQueryOp eOp ) +{ + sc::Compare aComp; + aComp.meOp = eOp; + aComp.mbIgnoreCase = mrDoc.GetDocOptions().IsIgnoreCase(); + for( short i = 1; i >= 0; i-- ) + { + sc::Compare::Cell& rCell = aComp.maCells[i]; + + switch ( GetRawStackType() ) + { + case svEmptyCell: + Pop(); + rCell.mbEmpty = true; + break; + case svMissing: + case svDouble: + rCell.mfValue = GetDouble(); + rCell.mbValue = true; + break; + case svString: + rCell.maStr = GetString(); + rCell.mbValue = false; + break; + case svDoubleRef : + case svSingleRef : + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + break; + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasEmptyValue()) + rCell.mbEmpty = true; + else if (aCell.hasString()) + { + svl::SharedString aStr; + GetCellString(aStr, aCell); + rCell.maStr = aStr; + rCell.mbValue = false; + } + else + { + rCell.mfValue = GetCellValue(aAdr, aCell); + rCell.mbValue = true; + } + } + break; + case svExternalSingleRef: + { + ScMatrixRef pMat = GetMatrix(); + if (!pMat) + { + SetError( FormulaError::IllegalParameter); + break; + } + + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + if (!nC || !nR) + { + SetError( FormulaError::IllegalParameter); + break; + } + if (pMat->IsEmpty(0, 0)) + rCell.mbEmpty = true; + else if (pMat->IsStringOrEmpty(0, 0)) + { + rCell.maStr = pMat->GetString(0, 0); + rCell.mbValue = false; + } + else + { + rCell.mfValue = pMat->GetDouble(0, 0); + rCell.mbValue = true; + } + } + break; + case svExternalDoubleRef: + // TODO: Find out how to handle this... + // Xcl generates a position dependent intersection using + // col/row, as it seems to do for all range references, not + // only in compare context. We'd need a general implementation + // for that behavior similar to svDoubleRef in scalar and array + // mode. Which also means we'd have to change all places where + // it currently is handled along with svMatrix. + default: + PopError(); + SetError( FormulaError::IllegalParameter); + break; + } + } + if( nGlobalError != FormulaError::NONE ) + return 0; + nCurFmtType = nFuncFmtType = SvNumFormatType::LOGICAL; + return sc::CompareFunc(aComp); +} + +sc::RangeMatrix ScInterpreter::CompareMat( ScQueryOp eOp, sc::CompareOptions* pOptions ) +{ + sc::Compare aComp; + aComp.meOp = eOp; + aComp.mbIgnoreCase = mrDoc.GetDocOptions().IsIgnoreCase(); + sc::RangeMatrix aMat[2]; + ScAddress aAdr; + for( short i = 1; i >= 0; i-- ) + { + sc::Compare::Cell& rCell = aComp.maCells[i]; + + switch (GetRawStackType()) + { + case svEmptyCell: + Pop(); + rCell.mbEmpty = true; + break; + case svMissing: + case svDouble: + rCell.mfValue = GetDouble(); + rCell.mbValue = true; + break; + case svString: + rCell.maStr = GetString(); + rCell.mbValue = false; + break; + case svSingleRef: + { + PopSingleRef( aAdr ); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasEmptyValue()) + rCell.mbEmpty = true; + else if (aCell.hasString()) + { + svl::SharedString aStr; + GetCellString(aStr, aCell); + rCell.maStr = aStr; + rCell.mbValue = false; + } + else + { + rCell.mfValue = GetCellValue(aAdr, aCell); + rCell.mbValue = true; + } + } + break; + case svExternalSingleRef: + case svExternalDoubleRef: + case svDoubleRef: + case svMatrix: + aMat[i] = GetRangeMatrix(); + if (!aMat[i].mpMat) + SetError( FormulaError::IllegalParameter); + else + aMat[i].mpMat->SetErrorInterpreter(nullptr); + // errors are transported as DoubleError inside matrix + break; + default: + PopError(); + SetError( FormulaError::IllegalParameter); + break; + } + } + + sc::RangeMatrix aRes; + + if (nGlobalError != FormulaError::NONE) + { + nCurFmtType = nFuncFmtType = SvNumFormatType::LOGICAL; + return aRes; + } + + if (aMat[0].mpMat && aMat[1].mpMat) + { + SCSIZE nC0, nC1; + SCSIZE nR0, nR1; + aMat[0].mpMat->GetDimensions(nC0, nR0); + aMat[1].mpMat->GetDimensions(nC1, nR1); + SCSIZE nC = std::max( nC0, nC1 ); + SCSIZE nR = std::max( nR0, nR1 ); + aRes.mpMat = GetNewMat( nC, nR, /*bEmpty*/true ); + if (!aRes.mpMat) + return aRes; + for ( SCSIZE j=0; j<nC; j++ ) + { + for ( SCSIZE k=0; k<nR; k++ ) + { + SCSIZE nCol = j, nRow = k; + if (aMat[0].mpMat->ValidColRowOrReplicated(nCol, nRow) && + aMat[1].mpMat->ValidColRowOrReplicated(nCol, nRow)) + { + for ( short i=1; i>=0; i-- ) + { + sc::Compare::Cell& rCell = aComp.maCells[i]; + + if (aMat[i].mpMat->IsStringOrEmpty(j, k)) + { + rCell.mbValue = false; + rCell.maStr = aMat[i].mpMat->GetString(j, k); + rCell.mbEmpty = aMat[i].mpMat->IsEmpty(j, k); + } + else + { + rCell.mbValue = true; + rCell.mfValue = aMat[i].mpMat->GetDouble(j, k); + rCell.mbEmpty = false; + } + } + aRes.mpMat->PutDouble( sc::CompareFunc( aComp, pOptions), j, k); + } + else + aRes.mpMat->PutError( FormulaError::NoValue, j, k); + } + } + + switch (eOp) + { + case SC_EQUAL: + aRes.mpMat->CompareEqual(); + break; + case SC_LESS: + aRes.mpMat->CompareLess(); + break; + case SC_GREATER: + aRes.mpMat->CompareGreater(); + break; + case SC_LESS_EQUAL: + aRes.mpMat->CompareLessEqual(); + break; + case SC_GREATER_EQUAL: + aRes.mpMat->CompareGreaterEqual(); + break; + case SC_NOT_EQUAL: + aRes.mpMat->CompareNotEqual(); + break; + default: + SAL_WARN("sc", "ScInterpreter::QueryMat: unhandled comparison operator: " << static_cast<int>(eOp)); + aRes.mpMat.reset(); + return aRes; + } + } + else if (aMat[0].mpMat || aMat[1].mpMat) + { + size_t i = ( aMat[0].mpMat ? 0 : 1); + + aRes.mnCol1 = aMat[i].mnCol1; + aRes.mnRow1 = aMat[i].mnRow1; + aRes.mnTab1 = aMat[i].mnTab1; + aRes.mnCol2 = aMat[i].mnCol2; + aRes.mnRow2 = aMat[i].mnRow2; + aRes.mnTab2 = aMat[i].mnTab2; + + ScMatrix& rMat = *aMat[i].mpMat; + aRes.mpMat = rMat.CompareMatrix(aComp, i, pOptions); + if (!aRes.mpMat) + return aRes; + } + + nCurFmtType = nFuncFmtType = SvNumFormatType::LOGICAL; + return aRes; +} + +ScMatrixRef ScInterpreter::QueryMat( const ScMatrixRef& pMat, sc::CompareOptions& rOptions ) +{ + SvNumFormatType nSaveCurFmtType = nCurFmtType; + SvNumFormatType nSaveFuncFmtType = nFuncFmtType; + PushMatrix( pMat); + const ScQueryEntry::Item& rItem = rOptions.aQueryEntry.GetQueryItem(); + if (rItem.meType == ScQueryEntry::ByString) + PushString(rItem.maString.getString()); + else + PushDouble(rItem.mfVal); + ScMatrixRef pResultMatrix = CompareMat(rOptions.aQueryEntry.eOp, &rOptions).mpMat; + nCurFmtType = nSaveCurFmtType; + nFuncFmtType = nSaveFuncFmtType; + if (nGlobalError != FormulaError::NONE || !pResultMatrix) + { + SetError( FormulaError::IllegalParameter); + return pResultMatrix; + } + + return pResultMatrix; +} + +void ScInterpreter::ScEqual() +{ + if ( GetStackType(1) == svMatrix || GetStackType(2) == svMatrix ) + { + sc::RangeMatrix aMat = CompareMat(SC_EQUAL); + if (!aMat.mpMat) + { + PushIllegalParameter(); + return; + } + + PushMatrix(aMat); + } + else + PushInt( int(Compare( SC_EQUAL) == 0) ); +} + +void ScInterpreter::ScNotEqual() +{ + if ( GetStackType(1) == svMatrix || GetStackType(2) == svMatrix ) + { + sc::RangeMatrix aMat = CompareMat(SC_NOT_EQUAL); + if (!aMat.mpMat) + { + PushIllegalParameter(); + return; + } + + PushMatrix(aMat); + } + else + PushInt( int(Compare( SC_NOT_EQUAL) != 0) ); +} + +void ScInterpreter::ScLess() +{ + if ( GetStackType(1) == svMatrix || GetStackType(2) == svMatrix ) + { + sc::RangeMatrix aMat = CompareMat(SC_LESS); + if (!aMat.mpMat) + { + PushIllegalParameter(); + return; + } + + PushMatrix(aMat); + } + else + PushInt( int(Compare( SC_LESS) < 0) ); +} + +void ScInterpreter::ScGreater() +{ + if ( GetStackType(1) == svMatrix || GetStackType(2) == svMatrix ) + { + sc::RangeMatrix aMat = CompareMat(SC_GREATER); + if (!aMat.mpMat) + { + PushIllegalParameter(); + return; + } + + PushMatrix(aMat); + } + else + PushInt( int(Compare( SC_GREATER) > 0) ); +} + +void ScInterpreter::ScLessEqual() +{ + if ( GetStackType(1) == svMatrix || GetStackType(2) == svMatrix ) + { + sc::RangeMatrix aMat = CompareMat(SC_LESS_EQUAL); + if (!aMat.mpMat) + { + PushIllegalParameter(); + return; + } + + PushMatrix(aMat); + } + else + PushInt( int(Compare( SC_LESS_EQUAL) <= 0) ); +} + +void ScInterpreter::ScGreaterEqual() +{ + if ( GetStackType(1) == svMatrix || GetStackType(2) == svMatrix ) + { + sc::RangeMatrix aMat = CompareMat(SC_GREATER_EQUAL); + if (!aMat.mpMat) + { + PushIllegalParameter(); + return; + } + + PushMatrix(aMat); + } + else + PushInt( int(Compare( SC_GREATER_EQUAL) >= 0) ); +} + +void ScInterpreter::ScAnd() +{ + nFuncFmtType = SvNumFormatType::LOGICAL; + short nParamCount = GetByte(); + if ( !MustHaveParamCountMin( nParamCount, 1 ) ) + return; + + bool bHaveValue = false; + bool bRes = true; + size_t nRefInList = 0; + while( nParamCount-- > 0) + { + if ( nGlobalError == FormulaError::NONE ) + { + switch ( GetStackType() ) + { + case svDouble : + bHaveValue = true; + bRes &= ( PopDouble() != 0.0 ); + break; + case svString : + Pop(); + SetError( FormulaError::NoValue ); + break; + case svSingleRef : + { + ScAddress aAdr; + PopSingleRef( aAdr ); + if ( nGlobalError == FormulaError::NONE ) + { + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + { + bHaveValue = true; + bRes &= ( GetCellValue(aAdr, aCell) != 0.0 ); + } + // else: Xcl raises no error here + } + } + break; + case svDoubleRef: + case svRefList: + { + ScRange aRange; + PopDoubleRef( aRange, nParamCount, nRefInList); + if ( nGlobalError == FormulaError::NONE ) + { + double fVal; + FormulaError nErr = FormulaError::NONE; + ScValueIterator aValIter( mrContext, aRange ); + if ( aValIter.GetFirst( fVal, nErr ) && nErr == FormulaError::NONE ) + { + bHaveValue = true; + do + { + bRes &= ( fVal != 0.0 ); + } while ( (nErr == FormulaError::NONE) && + aValIter.GetNext( fVal, nErr ) ); + } + SetError( nErr ); + } + } + break; + case svExternalSingleRef: + case svExternalDoubleRef: + case svMatrix: + { + ScMatrixRef pMat = GetMatrix(); + if ( pMat ) + { + bHaveValue = true; + double fVal = pMat->And(); + FormulaError nErr = GetDoubleErrorValue( fVal ); + if ( nErr != FormulaError::NONE ) + { + SetError( nErr ); + bRes = false; + } + else + bRes &= (fVal != 0.0); + } + // else: GetMatrix did set FormulaError::IllegalParameter + } + break; + default: + PopError(); + SetError( FormulaError::IllegalParameter); + } + } + else + Pop(); + } + if ( bHaveValue ) + PushInt( int(bRes) ); + else + PushNoValue(); +} + +void ScInterpreter::ScOr() +{ + nFuncFmtType = SvNumFormatType::LOGICAL; + short nParamCount = GetByte(); + if ( !MustHaveParamCountMin( nParamCount, 1 ) ) + return; + + bool bHaveValue = false; + bool bRes = false; + size_t nRefInList = 0; + while( nParamCount-- > 0) + { + if ( nGlobalError == FormulaError::NONE ) + { + switch ( GetStackType() ) + { + case svDouble : + bHaveValue = true; + bRes |= ( PopDouble() != 0.0 ); + break; + case svString : + Pop(); + SetError( FormulaError::NoValue ); + break; + case svSingleRef : + { + ScAddress aAdr; + PopSingleRef( aAdr ); + if ( nGlobalError == FormulaError::NONE ) + { + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + { + bHaveValue = true; + bRes |= ( GetCellValue(aAdr, aCell) != 0.0 ); + } + // else: Xcl raises no error here + } + } + break; + case svDoubleRef: + case svRefList: + { + ScRange aRange; + PopDoubleRef( aRange, nParamCount, nRefInList); + if ( nGlobalError == FormulaError::NONE ) + { + double fVal; + FormulaError nErr = FormulaError::NONE; + ScValueIterator aValIter( mrContext, aRange ); + if ( aValIter.GetFirst( fVal, nErr ) ) + { + bHaveValue = true; + do + { + bRes |= ( fVal != 0.0 ); + } while ( (nErr == FormulaError::NONE) && + aValIter.GetNext( fVal, nErr ) ); + } + SetError( nErr ); + } + } + break; + case svExternalSingleRef: + case svExternalDoubleRef: + case svMatrix: + { + bHaveValue = true; + ScMatrixRef pMat = GetMatrix(); + if ( pMat ) + { + bHaveValue = true; + double fVal = pMat->Or(); + FormulaError nErr = GetDoubleErrorValue( fVal ); + if ( nErr != FormulaError::NONE ) + { + SetError( nErr ); + bRes = false; + } + else + bRes |= (fVal != 0.0); + } + // else: GetMatrix did set FormulaError::IllegalParameter + } + break; + default: + PopError(); + SetError( FormulaError::IllegalParameter); + } + } + else + Pop(); + } + if ( bHaveValue ) + PushInt( int(bRes) ); + else + PushNoValue(); +} + +void ScInterpreter::ScXor() +{ + + nFuncFmtType = SvNumFormatType::LOGICAL; + short nParamCount = GetByte(); + if ( !MustHaveParamCountMin( nParamCount, 1 ) ) + return; + + bool bHaveValue = false; + bool bRes = false; + size_t nRefInList = 0; + while( nParamCount-- > 0) + { + if ( nGlobalError == FormulaError::NONE ) + { + switch ( GetStackType() ) + { + case svDouble : + bHaveValue = true; + bRes ^= ( PopDouble() != 0.0 ); + break; + case svString : + Pop(); + SetError( FormulaError::NoValue ); + break; + case svSingleRef : + { + ScAddress aAdr; + PopSingleRef( aAdr ); + if ( nGlobalError == FormulaError::NONE ) + { + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + { + bHaveValue = true; + bRes ^= ( GetCellValue(aAdr, aCell) != 0.0 ); + } + /* TODO: set error? Excel doesn't have XOR, but + * doesn't set an error in this case for AND and + * OR. */ + } + } + break; + case svDoubleRef: + case svRefList: + { + ScRange aRange; + PopDoubleRef( aRange, nParamCount, nRefInList); + if ( nGlobalError == FormulaError::NONE ) + { + double fVal; + FormulaError nErr = FormulaError::NONE; + ScValueIterator aValIter( mrContext, aRange ); + if ( aValIter.GetFirst( fVal, nErr ) ) + { + bHaveValue = true; + do + { + bRes ^= ( fVal != 0.0 ); + } while ( (nErr == FormulaError::NONE) && + aValIter.GetNext( fVal, nErr ) ); + } + SetError( nErr ); + } + } + break; + case svExternalSingleRef: + case svExternalDoubleRef: + case svMatrix: + { + bHaveValue = true; + ScMatrixRef pMat = GetMatrix(); + if ( pMat ) + { + bHaveValue = true; + double fVal = pMat->Xor(); + FormulaError nErr = GetDoubleErrorValue( fVal ); + if ( nErr != FormulaError::NONE ) + { + SetError( nErr ); + bRes = false; + } + else + bRes ^= ( fVal != 0.0 ); + } + // else: GetMatrix did set FormulaError::IllegalParameter + } + break; + default: + PopError(); + SetError( FormulaError::IllegalParameter); + } + } + else + Pop(); + } + if ( bHaveValue ) + PushInt( int(bRes) ); + else + PushNoValue(); +} + +void ScInterpreter::ScNeg() +{ + // Simple negation doesn't change current format type to number, keep + // current type. + nFuncFmtType = nCurFmtType; + switch ( GetStackType() ) + { + case svMatrix : + { + ScMatrixRef pMat = GetMatrix(); + if ( !pMat ) + PushIllegalParameter(); + else + { + SCSIZE nC, nR; + pMat->GetDimensions( nC, nR ); + ScMatrixRef pResMat = GetNewMat( nC, nR, /*bEmpty*/true ); + if ( !pResMat ) + PushIllegalArgument(); + else + { + pMat->NegOp( *pResMat); + PushMatrix( pResMat ); + } + } + } + break; + default: + PushDouble( -GetDouble() ); + } +} + +void ScInterpreter::ScPercentSign() +{ + nFuncFmtType = SvNumFormatType::PERCENT; + const FormulaToken* pSaveCur = pCur; + sal_uInt8 nSavePar = cPar; + PushInt( 100 ); + cPar = 2; + FormulaByteToken aDivOp( ocDiv, cPar ); + pCur = &aDivOp; + ScDiv(); + pCur = pSaveCur; + cPar = nSavePar; +} + +void ScInterpreter::ScNot() +{ + nFuncFmtType = SvNumFormatType::LOGICAL; + switch ( GetStackType() ) + { + case svMatrix : + { + ScMatrixRef pMat = GetMatrix(); + if ( !pMat ) + PushIllegalParameter(); + else + { + SCSIZE nC, nR; + pMat->GetDimensions( nC, nR ); + ScMatrixRef pResMat = GetNewMat( nC, nR, /*bEmpty*/true); + if ( !pResMat ) + PushIllegalArgument(); + else + { + pMat->NotOp( *pResMat); + PushMatrix( pResMat ); + } + } + } + break; + default: + PushInt( int(GetDouble() == 0.0) ); + } +} + +void ScInterpreter::ScBitAnd() +{ + + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + double num1 = ::rtl::math::approxFloor( GetDouble()); + double num2 = ::rtl::math::approxFloor( GetDouble()); + if ( (num1 >= n2power48) || (num1 < 0) || + (num2 >= n2power48) || (num2 < 0)) + PushIllegalArgument(); + else + PushDouble (static_cast<sal_uInt64>(num1) & static_cast<sal_uInt64>(num2)); +} + +void ScInterpreter::ScBitOr() +{ + + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + double num1 = ::rtl::math::approxFloor( GetDouble()); + double num2 = ::rtl::math::approxFloor( GetDouble()); + if ( (num1 >= n2power48) || (num1 < 0) || + (num2 >= n2power48) || (num2 < 0)) + PushIllegalArgument(); + else + PushDouble (static_cast<sal_uInt64>(num1) | static_cast<sal_uInt64>(num2)); +} + +void ScInterpreter::ScBitXor() +{ + + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + double num1 = ::rtl::math::approxFloor( GetDouble()); + double num2 = ::rtl::math::approxFloor( GetDouble()); + if ( (num1 >= n2power48) || (num1 < 0) || + (num2 >= n2power48) || (num2 < 0)) + PushIllegalArgument(); + else + PushDouble (static_cast<sal_uInt64>(num1) ^ static_cast<sal_uInt64>(num2)); +} + +void ScInterpreter::ScBitLshift() +{ + + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + double fShift = ::rtl::math::approxFloor( GetDouble()); + double num = ::rtl::math::approxFloor( GetDouble()); + if ((num >= n2power48) || (num < 0)) + PushIllegalArgument(); + else + { + double fRes; + if (fShift < 0) + fRes = ::rtl::math::approxFloor( num / pow( 2.0, -fShift)); + else if (fShift == 0) + fRes = num; + else + fRes = num * pow( 2.0, fShift); + PushDouble( fRes); + } +} + +void ScInterpreter::ScBitRshift() +{ + + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + double fShift = ::rtl::math::approxFloor( GetDouble()); + double num = ::rtl::math::approxFloor( GetDouble()); + if ((num >= n2power48) || (num < 0)) + PushIllegalArgument(); + else + { + double fRes; + if (fShift < 0) + fRes = num * pow( 2.0, -fShift); + else if (fShift == 0) + fRes = num; + else + fRes = ::rtl::math::approxFloor( num / pow( 2.0, fShift)); + PushDouble( fRes); + } +} + +void ScInterpreter::ScPi() +{ + PushDouble(M_PI); +} + +void ScInterpreter::ScRandomImpl( const std::function<double( double fFirst, double fLast )>& RandomFunc, + double fFirst, double fLast ) +{ + if (bMatrixFormula) + { + SCCOL nCols = 0; + SCROW nRows = 0; + // In JumpMatrix context use its dimensions for the return matrix; the + // formula cell range selected may differ, for example if the result is + // to be transposed. + if (GetStackType(1) == svJumpMatrix) + { + SCSIZE nC, nR; + pStack[sp-1]->GetJumpMatrix()->GetDimensions( nC, nR); + nCols = std::max<SCCOL>(0, static_cast<SCCOL>(nC)); + nRows = std::max<SCROW>(0, static_cast<SCROW>(nR)); + } + else if (pMyFormulaCell) + pMyFormulaCell->GetMatColsRows( nCols, nRows); + + if (nCols == 1 && nRows == 1) + { + // For compatibility with existing + // com.sun.star.sheet.FunctionAccess.callFunction() calls that per + // default are executed in array context unless + // FA.setPropertyValue("IsArrayFunction",False) was set, return a + // scalar double instead of a 1x1 matrix object. tdf#128218 + PushDouble( RandomFunc( fFirst, fLast)); + return; + } + + // ScViewFunc::EnterMatrix() might be asking for + // ScFormulaCell::GetResultDimensions(), which here are none so create + // a 1x1 matrix at least which exactly is the case when EnterMatrix() + // asks for a not selected range. + if (nCols == 0) + nCols = 1; + if (nRows == 0) + nRows = 1; + ScMatrixRef pResMat = GetNewMat( static_cast<SCSIZE>(nCols), static_cast<SCSIZE>(nRows), /*bEmpty*/true ); + if (!pResMat) + PushError( FormulaError::MatrixSize); + else + { + for (SCCOL i=0; i < nCols; ++i) + { + for (SCROW j=0; j < nRows; ++j) + { + pResMat->PutDouble( RandomFunc( fFirst, fLast), + static_cast<SCSIZE>(i), static_cast<SCSIZE>(j)); + } + } + PushMatrix( pResMat); + } + } + else + { + PushDouble( RandomFunc( fFirst, fLast)); + } +} + +void ScInterpreter::ScRandom() +{ + auto RandomFunc = []( double, double ) + { + return comphelper::rng::uniform_real_distribution(); + }; + ScRandomImpl( RandomFunc, 0.0, 0.0); +} + +void ScInterpreter::ScRandbetween() +{ + if (!MustHaveParamCount( GetByte(), 2)) + return; + + // Same like scaddins/source/analysis/analysis.cxx + // AnalysisAddIn::getRandbetween() + double fMax = rtl::math::round( GetDouble(), 0, rtl_math_RoundingMode_Up); + double fMin = rtl::math::round( GetDouble(), 0, rtl_math_RoundingMode_Up); + if (nGlobalError != FormulaError::NONE || fMin > fMax) + { + PushIllegalArgument(); + return; + } + fMax = std::nextafter( fMax+1, -DBL_MAX); + auto RandomFunc = []( double fFirst, double fLast ) + { + return floor( comphelper::rng::uniform_real_distribution( fFirst, fLast)); + }; + ScRandomImpl( RandomFunc, fMin, fMax); +} + +void ScInterpreter::ScTrue() +{ + nFuncFmtType = SvNumFormatType::LOGICAL; + PushInt(1); +} + +void ScInterpreter::ScFalse() +{ + nFuncFmtType = SvNumFormatType::LOGICAL; + PushInt(0); +} + +void ScInterpreter::ScDeg() +{ + PushDouble(basegfx::rad2deg(GetDouble())); +} + +void ScInterpreter::ScRad() +{ + PushDouble(basegfx::deg2rad(GetDouble())); +} + +void ScInterpreter::ScSin() +{ + PushDouble(::rtl::math::sin(GetDouble())); +} + +void ScInterpreter::ScCos() +{ + PushDouble(::rtl::math::cos(GetDouble())); +} + +void ScInterpreter::ScTan() +{ + PushDouble(::rtl::math::tan(GetDouble())); +} + +void ScInterpreter::ScCot() +{ + PushDouble(1.0 / ::rtl::math::tan(GetDouble())); +} + +void ScInterpreter::ScArcSin() +{ + PushDouble(asin(GetDouble())); +} + +void ScInterpreter::ScArcCos() +{ + PushDouble(acos(GetDouble())); +} + +void ScInterpreter::ScArcTan() +{ + PushDouble(atan(GetDouble())); +} + +void ScInterpreter::ScArcCot() +{ + PushDouble((M_PI_2) - atan(GetDouble())); +} + +void ScInterpreter::ScSinHyp() +{ + PushDouble(sinh(GetDouble())); +} + +void ScInterpreter::ScCosHyp() +{ + PushDouble(cosh(GetDouble())); +} + +void ScInterpreter::ScTanHyp() +{ + PushDouble(tanh(GetDouble())); +} + +void ScInterpreter::ScCotHyp() +{ + PushDouble(1.0 / tanh(GetDouble())); +} + +void ScInterpreter::ScArcSinHyp() +{ + PushDouble( ::rtl::math::asinh( GetDouble())); +} + +void ScInterpreter::ScArcCosHyp() +{ + double fVal = GetDouble(); + if (fVal < 1.0) + PushIllegalArgument(); + else + PushDouble( ::rtl::math::acosh( fVal)); +} + +void ScInterpreter::ScArcTanHyp() +{ + double fVal = GetDouble(); + if (fabs(fVal) >= 1.0) + PushIllegalArgument(); + else + PushDouble(::atanh(fVal)); +} + +void ScInterpreter::ScArcCotHyp() +{ + double nVal = GetDouble(); + if (fabs(nVal) <= 1.0) + PushIllegalArgument(); + else + PushDouble(0.5 * log((nVal + 1.0) / (nVal - 1.0))); +} + +void ScInterpreter::ScCosecant() +{ + PushDouble(1.0 / ::rtl::math::sin(GetDouble())); +} + +void ScInterpreter::ScSecant() +{ + PushDouble(1.0 / ::rtl::math::cos(GetDouble())); +} + +void ScInterpreter::ScCosecantHyp() +{ + PushDouble(1.0 / sinh(GetDouble())); +} + +void ScInterpreter::ScSecantHyp() +{ + PushDouble(1.0 / cosh(GetDouble())); +} + +void ScInterpreter::ScExp() +{ + PushDouble(exp(GetDouble())); +} + +void ScInterpreter::ScSqrt() +{ + double fVal = GetDouble(); + if (fVal >= 0.0) + PushDouble(sqrt(fVal)); + else + PushIllegalArgument(); +} + +void ScInterpreter::ScIsEmpty() +{ + short nRes = 0; + nFuncFmtType = SvNumFormatType::LOGICAL; + switch ( GetRawStackType() ) + { + case svEmptyCell: + { + FormulaConstTokenRef p = PopToken(); + if (!static_cast<const ScEmptyCellToken*>(p.get())->IsInherited()) + nRes = 1; + } + break; + case svDoubleRef : + case svSingleRef : + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + break; + // NOTE: this differs from COUNTBLANK() ScCountEmptyCells() that + // may treat ="" in the referenced cell as blank for Excel + // interoperability. + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.getType() == CELLTYPE_NONE) + nRes = 1; + } + break; + case svExternalSingleRef: + case svExternalDoubleRef: + case svMatrix: + { + ScMatrixRef pMat = GetMatrix(); + if ( !pMat ) + ; // nothing + else if ( !pJumpMatrix ) + nRes = pMat->IsEmptyCell( 0, 0) ? 1 : 0; + else + { + SCSIZE nCols, nRows, nC, nR; + pMat->GetDimensions( nCols, nRows); + pJumpMatrix->GetPos( nC, nR); + if ( nC < nCols && nR < nRows ) + nRes = pMat->IsEmptyCell( nC, nR) ? 1 : 0; + // else: false, not empty (which is what Xcl does) + } + } + break; + default: + Pop(); + } + nGlobalError = FormulaError::NONE; + PushInt( nRes ); +} + +bool ScInterpreter::IsString() +{ + nFuncFmtType = SvNumFormatType::LOGICAL; + bool bRes = false; + switch ( GetRawStackType() ) + { + case svString: + Pop(); + bRes = true; + break; + case svDoubleRef : + case svSingleRef : + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + break; + + ScRefCellValue aCell(mrDoc, aAdr); + if (GetCellErrCode(aCell) == FormulaError::NONE) + { + switch (aCell.getType()) + { + case CELLTYPE_STRING : + case CELLTYPE_EDIT : + bRes = true; + break; + case CELLTYPE_FORMULA : + bRes = (!aCell.getFormula()->IsValue() && !aCell.getFormula()->IsEmpty()); + break; + default: + ; // nothing + } + } + } + break; + case svExternalSingleRef: + { + ScExternalRefCache::TokenRef pToken; + PopExternalSingleRef(pToken); + if (nGlobalError == FormulaError::NONE && pToken->GetType() == svString) + bRes = true; + } + break; + case svExternalDoubleRef: + case svMatrix: + { + ScMatrixRef pMat = GetMatrix(); + if ( !pMat ) + ; // nothing + else if ( !pJumpMatrix ) + bRes = pMat->IsStringOrEmpty(0, 0) && !pMat->IsEmpty(0, 0); + else + { + SCSIZE nCols, nRows, nC, nR; + pMat->GetDimensions( nCols, nRows); + pJumpMatrix->GetPos( nC, nR); + if ( nC < nCols && nR < nRows ) + bRes = pMat->IsStringOrEmpty( nC, nR) && !pMat->IsEmpty( nC, nR); + } + } + break; + default: + Pop(); + } + nGlobalError = FormulaError::NONE; + return bRes; +} + +void ScInterpreter::ScIsString() +{ + PushInt( int(IsString()) ); +} + +void ScInterpreter::ScIsNonString() +{ + PushInt( int(!IsString()) ); +} + +void ScInterpreter::ScIsLogical() +{ + bool bRes = false; + switch ( GetStackType() ) + { + case svDoubleRef : + case svSingleRef : + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + break; + + ScRefCellValue aCell(mrDoc, aAdr); + if (GetCellErrCode(aCell) == FormulaError::NONE) + { + if (aCell.hasNumeric()) + { + sal_uInt32 nFormat = GetCellNumberFormat(aAdr, aCell); + bRes = (pFormatter->GetType(nFormat) == SvNumFormatType::LOGICAL); + } + } + } + break; + case svMatrix: + { + double fVal; + svl::SharedString aStr; + ScMatValType nMatValType = GetDoubleOrStringFromMatrix( fVal, aStr); + bRes = (nMatValType == ScMatValType::Boolean); + } + break; + default: + PopError(); + if ( nGlobalError == FormulaError::NONE ) + bRes = ( nCurFmtType == SvNumFormatType::LOGICAL ); + } + nCurFmtType = nFuncFmtType = SvNumFormatType::LOGICAL; + nGlobalError = FormulaError::NONE; + PushInt( int(bRes) ); +} + +void ScInterpreter::ScType() +{ + short nType = 0; + switch ( GetStackType() ) + { + case svDoubleRef : + case svSingleRef : + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + break; + + ScRefCellValue aCell(mrDoc, aAdr); + if (GetCellErrCode(aCell) == FormulaError::NONE) + { + switch (aCell.getType()) + { + // NOTE: this is Xcl nonsense! + case CELLTYPE_STRING : + case CELLTYPE_EDIT : + nType = 2; + break; + case CELLTYPE_VALUE : + { + sal_uInt32 nFormat = GetCellNumberFormat(aAdr, aCell); + if (pFormatter->GetType(nFormat) == SvNumFormatType::LOGICAL) + nType = 4; + else + nType = 1; + } + break; + case CELLTYPE_NONE: + // always 1, s. tdf#73078 + nType = 1; + break; + case CELLTYPE_FORMULA : + nType = 8; + break; + default: + PushIllegalArgument(); + } + } + else + nType = 16; + } + break; + case svString: + PopError(); + if ( nGlobalError != FormulaError::NONE ) + { + nType = 16; + nGlobalError = FormulaError::NONE; + } + else + nType = 2; + break; + case svMatrix: + PopMatrix(); + if ( nGlobalError != FormulaError::NONE ) + { + nType = 16; + nGlobalError = FormulaError::NONE; + } + else + nType = 64; + // we could return the type of one element if in JumpMatrix or + // ForceArray mode, but Xcl doesn't ... + break; + default: + PopError(); + if ( nGlobalError != FormulaError::NONE ) + { + nType = 16; + nGlobalError = FormulaError::NONE; + } + else + nType = 1; + } + PushInt( nType ); +} + +static bool lcl_FormatHasNegColor( const SvNumberformat* pFormat ) +{ + return pFormat && pFormat->GetColor( 1 ); +} + +static bool lcl_FormatHasOpenPar( const SvNumberformat* pFormat ) +{ + return pFormat && (pFormat->GetFormatstring().indexOf('(') != -1); +} + +namespace { + +void getFormatString(const SvNumberFormatter* pFormatter, sal_uLong nFormat, OUString& rFmtStr) +{ + rFmtStr = pFormatter->GetCalcCellReturn( nFormat); +} + +} + +void ScInterpreter::ScCell() +{ // ATTRIBUTE ; [REF] + sal_uInt8 nParamCount = GetByte(); + if( !MustHaveParamCount( nParamCount, 1, 2 ) ) + return; + + ScAddress aCellPos( aPos ); + if( nParamCount == 2 ) + { + switch (GetStackType()) + { + case svExternalSingleRef: + case svExternalDoubleRef: + { + // Let's handle external reference separately... + ScCellExternal(); + return; + } + case svDoubleRef: + { + // Exceptionally not an intersecting position but top left. + // See ODF v1.3 part 4 OpenFormula 6.13.3 CELL + ScRange aRange; + PopDoubleRef( aRange); + aCellPos = aRange.aStart; + } + break; + case svSingleRef: + PopSingleRef( aCellPos); + break; + default: + PopError(); + SetError( FormulaError::NoRef); + } + } + OUString aInfoType = GetString().getString(); + if (nGlobalError != FormulaError::NONE) + PushIllegalParameter(); + else + { + ScRefCellValue aCell(mrDoc, aCellPos); + + ScCellKeywordTranslator::transKeyword(aInfoType, &ScGlobal::GetLocale(), ocCell); + +// *** ADDRESS INFO *** + if( aInfoType == "COL" ) + { // column number (1-based) + PushInt( aCellPos.Col() + 1 ); + } + else if( aInfoType == "ROW" ) + { // row number (1-based) + PushInt( aCellPos.Row() + 1 ); + } + else if( aInfoType == "SHEET" ) + { // table number (1-based) + PushInt( aCellPos.Tab() + 1 ); + } + else if( aInfoType == "ADDRESS" ) + { // address formatted as [['FILENAME'#]$TABLE.]$COL$ROW + + // Follow the configurable string reference address syntax as also + // used by INDIRECT() (and ADDRESS() for the sheet separator). + FormulaGrammar::AddressConvention eConv = maCalcConfig.meStringRefAddressSyntax; + switch (eConv) + { + default: + // Use the current address syntax if unspecified or says + // one or the other or one we don't explicitly handle. + eConv = mrDoc.GetAddressConvention(); + break; + case FormulaGrammar::CONV_OOO: + case FormulaGrammar::CONV_XL_A1: + case FormulaGrammar::CONV_XL_R1C1: + // Use that. + break; + } + + ScRefFlags nFlags = (aCellPos.Tab() == aPos.Tab()) ? ScRefFlags::ADDR_ABS : ScRefFlags::ADDR_ABS_3D; + OUString aStr(aCellPos.Format(nFlags, &mrDoc, eConv)); + PushString(aStr); + } + else if( aInfoType == "FILENAME" ) + { + SCTAB nTab = aCellPos.Tab(); + OUString aFuncResult; + if( nTab < mrDoc.GetTableCount() ) + { + if( mrDoc.GetLinkMode( nTab ) == ScLinkMode::VALUE ) + mrDoc.GetName( nTab, aFuncResult ); + else + { + ScDocShell* pShell = mrDoc.GetDocumentShell(); + if( pShell && pShell->GetMedium() ) + { + const INetURLObject& rURLObj = pShell->GetMedium()->GetURLObject(); + OUString aTabName; + mrDoc.GetName( nTab, aTabName ); + + FormulaGrammar::AddressConvention eConv = maCalcConfig.meStringRefAddressSyntax; + if (eConv == FormulaGrammar::CONV_UNSPECIFIED) + eConv = mrDoc.GetAddressConvention(); + + if (eConv == FormulaGrammar::CONV_XL_A1 || + eConv == FormulaGrammar::CONV_XL_R1C1 || + eConv == FormulaGrammar::CONV_XL_OOX) + { + // file name and table name: FILEPATH/[FILENAME]TABLE + aFuncResult = rURLObj.GetPartBeforeLastName() + + "[" + rURLObj.GetLastName(INetURLObject::DecodeMechanism::Unambiguous) + + "]" + aTabName; + } + else + { + // file name and table name: 'FILEPATH/FILENAME'#$TABLE + aFuncResult = "'" + + rURLObj.GetMainURL(INetURLObject::DecodeMechanism::Unambiguous) + + "'#$" + aTabName; + } + } + } + } + PushString( aFuncResult ); + } + else if( aInfoType == "COORD" ) + { // address, lotus 1-2-3 formatted: $TABLE:$COL$ROW + // Yes, passing tab as col is intentional! + OUString aCellStr1 = + ScAddress( static_cast<SCCOL>(aCellPos.Tab()), 0, 0 ).Format( + (ScRefFlags::COL_ABS|ScRefFlags::COL_VALID), nullptr, mrDoc.GetAddressConvention() ); + OUString aCellStr2 = + aCellPos.Format((ScRefFlags::COL_ABS|ScRefFlags::COL_VALID|ScRefFlags::ROW_ABS|ScRefFlags::ROW_VALID), + nullptr, mrDoc.GetAddressConvention()); + OUString aFuncResult = aCellStr1 + ":" + aCellStr2; + PushString( aFuncResult ); + } + +// *** CELL PROPERTIES *** + else if( aInfoType == "CONTENTS" ) + { // contents of the cell, no formatting + if (aCell.hasString()) + { + svl::SharedString aStr; + GetCellString(aStr, aCell); + PushString( aStr ); + } + else + PushDouble(GetCellValue(aCellPos, aCell)); + } + else if( aInfoType == "TYPE" ) + { // b = blank; l = string (label); v = otherwise (value) + sal_Unicode c; + if (aCell.hasString()) + c = 'l'; + else + c = aCell.hasNumeric() ? 'v' : 'b'; + PushString( OUString(c) ); + } + else if( aInfoType == "WIDTH" ) + { // column width (rounded off as count of zero characters in standard font and size) + Printer* pPrinter = mrDoc.GetPrinter(); + MapMode aOldMode( pPrinter->GetMapMode() ); + vcl::Font aOldFont( pPrinter->GetFont() ); + vcl::Font aDefFont; + + pPrinter->SetMapMode(MapMode(MapUnit::MapTwip)); + // font color doesn't matter here + mrDoc.GetDefPattern()->fillFontOnly(aDefFont, pPrinter); + pPrinter->SetFont(aDefFont); + tools::Long nZeroWidth = pPrinter->GetTextWidth( OUString( '0' ) ); + assert(nZeroWidth != 0); + pPrinter->SetFont( aOldFont ); + pPrinter->SetMapMode( aOldMode ); + int nZeroCount = static_cast<int>(mrDoc.GetColWidth( aCellPos.Col(), aCellPos.Tab() ) / nZeroWidth); + PushInt( nZeroCount ); + } + else if( aInfoType == "PREFIX" ) + { // ' = left; " = right; ^ = centered + sal_Unicode c = 0; + if (aCell.hasString()) + { + const SvxHorJustifyItem* pJustAttr = mrDoc.GetAttr( aCellPos, ATTR_HOR_JUSTIFY ); + switch( pJustAttr->GetValue() ) + { + case SvxCellHorJustify::Standard: + case SvxCellHorJustify::Left: + case SvxCellHorJustify::Block: c = '\''; break; + case SvxCellHorJustify::Center: c = '^'; break; + case SvxCellHorJustify::Right: c = '"'; break; + case SvxCellHorJustify::Repeat: c = '\\'; break; + } + } + PushString( OUString(c) ); + } + else if( aInfoType == "PROTECT" ) + { // 1 = cell locked + const ScProtectionAttr* pProtAttr = mrDoc.GetAttr( aCellPos, ATTR_PROTECTION ); + PushInt( pProtAttr->GetProtection() ? 1 : 0 ); + } + +// *** FORMATTING *** + else if( aInfoType == "FORMAT" ) + { // specific format code for standard formats + OUString aFuncResult; + sal_uInt32 nFormat = mrDoc.GetNumberFormat( aCellPos ); + getFormatString(pFormatter, nFormat, aFuncResult); + PushString( aFuncResult ); + } + else if( aInfoType == "COLOR" ) + { // 1 = negative values are colored, otherwise 0 + const SvNumberformat* pFormat = pFormatter->GetEntry( mrDoc.GetNumberFormat( aCellPos ) ); + PushInt( lcl_FormatHasNegColor( pFormat ) ? 1 : 0 ); + } + else if( aInfoType == "PARENTHESES" ) + { // 1 = format string contains a '(' character, otherwise 0 + const SvNumberformat* pFormat = pFormatter->GetEntry( mrDoc.GetNumberFormat( aCellPos ) ); + PushInt( lcl_FormatHasOpenPar( pFormat ) ? 1 : 0 ); + } + else + PushIllegalArgument(); + } +} + +void ScInterpreter::ScCellExternal() +{ + sal_uInt16 nFileId; + OUString aTabName; + ScSingleRefData aRef; + ScExternalRefCache::TokenRef pToken; + ScExternalRefCache::CellFormat aFmt; + PopExternalSingleRef(nFileId, aTabName, aRef, pToken, &aFmt); + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + + OUString aInfoType = GetString().getString(); + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + + SCCOL nCol; + SCROW nRow; + SCTAB nTab; + aRef.SetAbsTab(0); // external ref has a tab index of -1, which SingleRefToVars() don't like. + SingleRefToVars(aRef, nCol, nRow, nTab); + if (nGlobalError != FormulaError::NONE) + { + PushIllegalParameter(); + return; + } + aRef.SetAbsTab(-1); // revert the value. + + ScCellKeywordTranslator::transKeyword(aInfoType, &ScGlobal::GetLocale(), ocCell); + ScExternalRefManager* pRefMgr = mrDoc.GetExternalRefManager(); + + if ( aInfoType == "COL" ) + PushInt(nCol + 1); + else if ( aInfoType == "ROW" ) + PushInt(nRow + 1); + else if ( aInfoType == "SHEET" ) + { + // For SHEET, No idea what number we should set, but let's always set + // 1 if the external sheet exists, no matter what sheet. Excel does + // the same. + if (pRefMgr->getCacheTable(nFileId, aTabName, false)) + PushInt(1); + else + SetError(FormulaError::NoName); + } + else if ( aInfoType == "ADDRESS" ) + { + // ODF 1.2 says we need to always display address using the ODF A1 grammar. + ScTokenArray aArray(mrDoc); + aArray.AddExternalSingleReference(nFileId, svl::SharedString( aTabName), aRef); // string not interned + ScCompiler aComp(mrDoc, aPos, aArray, formula::FormulaGrammar::GRAM_ODFF_A1); + OUString aStr; + aComp.CreateStringFromTokenArray(aStr); + PushString(aStr); + } + else if ( aInfoType == "FILENAME" ) + { + const OUString* p = pRefMgr->getExternalFileName(nFileId); + if (!p) + { + // In theory this should never happen... + SetError(FormulaError::NoName); + return; + } + + OUString aBuf; + FormulaGrammar::AddressConvention eConv = maCalcConfig.meStringRefAddressSyntax; + if (eConv == FormulaGrammar::CONV_UNSPECIFIED) + eConv = mrDoc.GetAddressConvention(); + + if (eConv == FormulaGrammar::CONV_XL_A1 || + eConv == FormulaGrammar::CONV_XL_R1C1 || + eConv == FormulaGrammar::CONV_XL_OOX) + { + // 'file URI/[FileName]SheetName + sal_Int32 nPos = p->lastIndexOf('/'); + aBuf = OUString::Concat(p->subView(0, nPos + 1)) + + "[" + p->subView(nPos + 1) + "]" + + aTabName; + } + else + { + // 'file URI'#$SheetName + aBuf = "'" + *p + "'#$" + aTabName; + } + + PushString(aBuf); + } + else if ( aInfoType == "CONTENTS" ) + { + switch (pToken->GetType()) + { + case svString: + PushString(pToken->GetString()); + break; + case svDouble: + PushString(OUString::number(pToken->GetDouble())); + break; + case svError: + PushString(ScGlobal::GetErrorString(pToken->GetError())); + break; + default: + PushString(OUString()); + } + } + else if ( aInfoType == "TYPE" ) + { + sal_Unicode c = 'v'; + switch (pToken->GetType()) + { + case svString: + c = 'l'; + break; + case svEmptyCell: + c = 'b'; + break; + default: + ; + } + PushString(OUString(c)); + } + else if ( aInfoType == "FORMAT" ) + { + OUString aFmtStr; + sal_uLong nFmt = aFmt.mbIsSet ? aFmt.mnIndex : 0; + getFormatString(pFormatter, nFmt, aFmtStr); + PushString(aFmtStr); + } + else if ( aInfoType == "COLOR" ) + { + // 1 = negative values are colored, otherwise 0 + int nVal = 0; + if (aFmt.mbIsSet) + { + const SvNumberformat* pFormat = pFormatter->GetEntry(aFmt.mnIndex); + nVal = lcl_FormatHasNegColor(pFormat) ? 1 : 0; + } + PushInt(nVal); + } + else if ( aInfoType == "PARENTHESES" ) + { + // 1 = format string contains a '(' character, otherwise 0 + int nVal = 0; + if (aFmt.mbIsSet) + { + const SvNumberformat* pFormat = pFormatter->GetEntry(aFmt.mnIndex); + nVal = lcl_FormatHasOpenPar(pFormat) ? 1 : 0; + } + PushInt(nVal); + } + else + PushIllegalParameter(); +} + +void ScInterpreter::ScIsRef() +{ + nFuncFmtType = SvNumFormatType::LOGICAL; + bool bRes = false; + switch ( GetStackType() ) + { + case svSingleRef : + { + ScAddress aAdr; + PopSingleRef( aAdr ); + if ( nGlobalError == FormulaError::NONE ) + bRes = true; + } + break; + case svDoubleRef : + { + ScRange aRange; + PopDoubleRef( aRange ); + if ( nGlobalError == FormulaError::NONE ) + bRes = true; + } + break; + case svRefList : + { + FormulaConstTokenRef x = PopToken(); + if ( nGlobalError == FormulaError::NONE ) + bRes = !x->GetRefList()->empty(); + } + break; + case svExternalSingleRef: + { + ScExternalRefCache::TokenRef pToken; + PopExternalSingleRef(pToken); + if (nGlobalError == FormulaError::NONE) + bRes = true; + } + break; + case svExternalDoubleRef: + { + ScExternalRefCache::TokenArrayRef pArray; + PopExternalDoubleRef(pArray); + if (nGlobalError == FormulaError::NONE) + bRes = true; + } + break; + default: + Pop(); + } + nGlobalError = FormulaError::NONE; + PushInt( int(bRes) ); +} + +void ScInterpreter::ScIsValue() +{ + nFuncFmtType = SvNumFormatType::LOGICAL; + bool bRes = false; + switch ( GetRawStackType() ) + { + case svDouble: + Pop(); + bRes = true; + break; + case svDoubleRef : + case svSingleRef : + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + break; + + ScRefCellValue aCell(mrDoc, aAdr); + if (GetCellErrCode(aCell) == FormulaError::NONE) + { + switch (aCell.getType()) + { + case CELLTYPE_VALUE : + bRes = true; + break; + case CELLTYPE_FORMULA : + bRes = (aCell.getFormula()->IsValue() && !aCell.getFormula()->IsEmpty()); + break; + default: + ; // nothing + } + } + } + break; + case svExternalSingleRef: + { + ScExternalRefCache::TokenRef pToken; + PopExternalSingleRef(pToken); + if (nGlobalError == FormulaError::NONE && pToken->GetType() == svDouble) + bRes = true; + } + break; + case svExternalDoubleRef: + case svMatrix: + { + ScMatrixRef pMat = GetMatrix(); + if ( !pMat ) + ; // nothing + else if ( !pJumpMatrix ) + { + if (pMat->GetErrorIfNotString( 0, 0) == FormulaError::NONE) + bRes = pMat->IsValue( 0, 0); + } + else + { + SCSIZE nCols, nRows, nC, nR; + pMat->GetDimensions( nCols, nRows); + pJumpMatrix->GetPos( nC, nR); + if ( nC < nCols && nR < nRows ) + if (pMat->GetErrorIfNotString( nC, nR) == FormulaError::NONE) + bRes = pMat->IsValue( nC, nR); + } + } + break; + default: + Pop(); + } + nGlobalError = FormulaError::NONE; + PushInt( int(bRes) ); +} + +void ScInterpreter::ScIsFormula() +{ + nFuncFmtType = SvNumFormatType::LOGICAL; + bool bRes = false; + switch ( GetStackType() ) + { + case svDoubleRef : + if (IsInArrayContext()) + { + SCCOL nCol1, nCol2; + SCROW nRow1, nRow2; + SCTAB nTab1, nTab2; + PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + if (nTab1 != nTab2) + { + PushIllegalArgument(); + return; + } + + ScMatrixRef pResMat = GetNewMat( static_cast<SCSIZE>(nCol2 - nCol1 + 1), + static_cast<SCSIZE>(nRow2 - nRow1 + 1), true); + if (!pResMat) + { + PushError( FormulaError::MatrixSize); + return; + } + + /* TODO: we really should have a gap-aware cell iterator. */ + SCSIZE i=0, j=0; + ScAddress aAdr( 0, 0, nTab1); + for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol) + { + aAdr.SetCol(nCol); + for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow) + { + aAdr.SetRow(nRow); + ScRefCellValue aCell(mrDoc, aAdr); + pResMat->PutBoolean( (aCell.getType() == CELLTYPE_FORMULA), i,j); + ++j; + } + ++i; + j = 0; + } + + PushMatrix( pResMat); + return; + } + [[fallthrough]]; + case svSingleRef : + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + break; + + bRes = (mrDoc.GetCellType(aAdr) == CELLTYPE_FORMULA); + } + break; + default: + Pop(); + } + nGlobalError = FormulaError::NONE; + PushInt( int(bRes) ); +} + +void ScInterpreter::ScFormula() +{ + OUString aFormula; + switch ( GetStackType() ) + { + case svDoubleRef : + if (IsInArrayContext()) + { + SCCOL nCol1, nCol2; + SCROW nRow1, nRow2; + SCTAB nTab1, nTab2; + PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + if (nGlobalError != FormulaError::NONE) + break; + + if (nTab1 != nTab2) + { + SetError( FormulaError::IllegalArgument); + break; + } + + ScMatrixRef pResMat = GetNewMat( nCol2 - nCol1 + 1, nRow2 - nRow1 + 1, true); + if (!pResMat) + break; + + /* TODO: use a column iterator instead? */ + SCSIZE i=0, j=0; + ScAddress aAdr(0,0,nTab1); + for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol) + { + aAdr.SetCol(nCol); + for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow) + { + aAdr.SetRow(nRow); + ScRefCellValue aCell(mrDoc, aAdr); + switch (aCell.getType()) + { + case CELLTYPE_FORMULA : + aFormula = aCell.getFormula()->GetFormula(formula::FormulaGrammar::GRAM_UNSPECIFIED, &mrContext); + pResMat->PutString( mrStrPool.intern( aFormula), i,j); + break; + default: + pResMat->PutError( FormulaError::NotAvailable, i,j); + } + ++j; + } + ++i; + j = 0; + } + + PushMatrix( pResMat); + return; + } + [[fallthrough]]; + case svSingleRef : + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + break; + + ScRefCellValue aCell(mrDoc, aAdr); + switch (aCell.getType()) + { + case CELLTYPE_FORMULA : + aFormula = aCell.getFormula()->GetFormula(formula::FormulaGrammar::GRAM_UNSPECIFIED, &mrContext); + break; + default: + SetError( FormulaError::NotAvailable ); + } + } + break; + default: + PopError(); + SetError( FormulaError::NotAvailable ); + } + PushString( aFormula ); +} + +void ScInterpreter::ScIsNV() +{ + nFuncFmtType = SvNumFormatType::LOGICAL; + bool bRes = false; + switch ( GetStackType() ) + { + case svDoubleRef : + case svSingleRef : + { + ScAddress aAdr; + bool bOk = PopDoubleRefOrSingleRef( aAdr ); + if ( nGlobalError == FormulaError::NotAvailable ) + bRes = true; + else if (bOk) + { + ScRefCellValue aCell(mrDoc, aAdr); + FormulaError nErr = GetCellErrCode(aCell); + bRes = (nErr == FormulaError::NotAvailable); + } + } + break; + case svExternalSingleRef: + { + ScExternalRefCache::TokenRef pToken; + PopExternalSingleRef(pToken); + if (nGlobalError == FormulaError::NotAvailable || + (pToken && pToken->GetType() == svError && pToken->GetError() == FormulaError::NotAvailable)) + bRes = true; + } + break; + case svExternalDoubleRef: + case svMatrix: + { + ScMatrixRef pMat = GetMatrix(); + if ( !pMat ) + ; // nothing + else if ( !pJumpMatrix ) + bRes = (pMat->GetErrorIfNotString( 0, 0) == FormulaError::NotAvailable); + else + { + SCSIZE nCols, nRows, nC, nR; + pMat->GetDimensions( nCols, nRows); + pJumpMatrix->GetPos( nC, nR); + if ( nC < nCols && nR < nRows ) + bRes = (pMat->GetErrorIfNotString( nC, nR) == FormulaError::NotAvailable); + } + } + break; + default: + PopError(); + if ( nGlobalError == FormulaError::NotAvailable ) + bRes = true; + } + nGlobalError = FormulaError::NONE; + PushInt( int(bRes) ); +} + +void ScInterpreter::ScIsErr() +{ + nFuncFmtType = SvNumFormatType::LOGICAL; + bool bRes = false; + switch ( GetStackType() ) + { + case svDoubleRef : + case svSingleRef : + { + ScAddress aAdr; + bool bOk = PopDoubleRefOrSingleRef( aAdr ); + if ( !bOk || (nGlobalError != FormulaError::NONE && nGlobalError != FormulaError::NotAvailable) ) + bRes = true; + else + { + ScRefCellValue aCell(mrDoc, aAdr); + FormulaError nErr = GetCellErrCode(aCell); + bRes = (nErr != FormulaError::NONE && nErr != FormulaError::NotAvailable); + } + } + break; + case svExternalSingleRef: + { + ScExternalRefCache::TokenRef pToken; + PopExternalSingleRef(pToken); + if ((nGlobalError != FormulaError::NONE && nGlobalError != FormulaError::NotAvailable) || !pToken || + (pToken->GetType() == svError && pToken->GetError() != FormulaError::NotAvailable)) + bRes = true; + } + break; + case svExternalDoubleRef: + case svMatrix: + { + ScMatrixRef pMat = GetMatrix(); + if ( nGlobalError != FormulaError::NONE || !pMat ) + bRes = ((nGlobalError != FormulaError::NONE && nGlobalError != FormulaError::NotAvailable) || !pMat); + else if ( !pJumpMatrix ) + { + FormulaError nErr = pMat->GetErrorIfNotString( 0, 0); + bRes = (nErr != FormulaError::NONE && nErr != FormulaError::NotAvailable); + } + else + { + SCSIZE nCols, nRows, nC, nR; + pMat->GetDimensions( nCols, nRows); + pJumpMatrix->GetPos( nC, nR); + if ( nC < nCols && nR < nRows ) + { + FormulaError nErr = pMat->GetErrorIfNotString( nC, nR); + bRes = (nErr != FormulaError::NONE && nErr != FormulaError::NotAvailable); + } + } + } + break; + default: + PopError(); + if ( nGlobalError != FormulaError::NONE && nGlobalError != FormulaError::NotAvailable ) + bRes = true; + } + nGlobalError = FormulaError::NONE; + PushInt( int(bRes) ); +} + +void ScInterpreter::ScIsError() +{ + nFuncFmtType = SvNumFormatType::LOGICAL; + bool bRes = false; + switch ( GetStackType() ) + { + case svDoubleRef : + case svSingleRef : + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + { + bRes = true; + break; + } + if ( nGlobalError != FormulaError::NONE ) + bRes = true; + else + { + ScRefCellValue aCell(mrDoc, aAdr); + bRes = (GetCellErrCode(aCell) != FormulaError::NONE); + } + } + break; + case svExternalSingleRef: + { + ScExternalRefCache::TokenRef pToken; + PopExternalSingleRef(pToken); + if (nGlobalError != FormulaError::NONE || pToken->GetType() == svError) + bRes = true; + } + break; + case svExternalDoubleRef: + case svMatrix: + { + ScMatrixRef pMat = GetMatrix(); + if ( nGlobalError != FormulaError::NONE || !pMat ) + bRes = true; + else if ( !pJumpMatrix ) + bRes = (pMat->GetErrorIfNotString( 0, 0) != FormulaError::NONE); + else + { + SCSIZE nCols, nRows, nC, nR; + pMat->GetDimensions( nCols, nRows); + pJumpMatrix->GetPos( nC, nR); + if ( nC < nCols && nR < nRows ) + bRes = (pMat->GetErrorIfNotString( nC, nR) != FormulaError::NONE); + } + } + break; + default: + PopError(); + if ( nGlobalError != FormulaError::NONE ) + bRes = true; + } + nGlobalError = FormulaError::NONE; + PushInt( int(bRes) ); +} + +bool ScInterpreter::IsEven() +{ + nFuncFmtType = SvNumFormatType::LOGICAL; + bool bRes = false; + double fVal = 0.0; + switch ( GetStackType() ) + { + case svDoubleRef : + case svSingleRef : + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + break; + + ScRefCellValue aCell(mrDoc, aAdr); + FormulaError nErr = GetCellErrCode(aCell); + if (nErr != FormulaError::NONE) + SetError(nErr); + else + { + switch (aCell.getType()) + { + case CELLTYPE_VALUE : + fVal = GetCellValue(aAdr, aCell); + bRes = true; + break; + case CELLTYPE_FORMULA : + if (aCell.getFormula()->IsValue()) + { + fVal = GetCellValue(aAdr, aCell); + bRes = true; + } + break; + default: + ; // nothing + } + } + } + break; + case svDouble: + { + fVal = PopDouble(); + bRes = true; + } + break; + case svExternalSingleRef: + { + ScExternalRefCache::TokenRef pToken; + PopExternalSingleRef(pToken); + if (nGlobalError == FormulaError::NONE && pToken->GetType() == svDouble) + { + fVal = pToken->GetDouble(); + bRes = true; + } + } + break; + case svExternalDoubleRef: + case svMatrix: + { + ScMatrixRef pMat = GetMatrix(); + if ( !pMat ) + ; // nothing + else if ( !pJumpMatrix ) + { + bRes = pMat->IsValue( 0, 0); + if ( bRes ) + fVal = pMat->GetDouble( 0, 0); + } + else + { + SCSIZE nCols, nRows, nC, nR; + pMat->GetDimensions( nCols, nRows); + pJumpMatrix->GetPos( nC, nR); + if ( nC < nCols && nR < nRows ) + { + bRes = pMat->IsValue( nC, nR); + if ( bRes ) + fVal = pMat->GetDouble( nC, nR); + } + else + SetError( FormulaError::NoValue); + } + } + break; + default: + ; // nothing + } + if ( !bRes ) + SetError( FormulaError::IllegalParameter); + else + bRes = ( fmod( ::rtl::math::approxFloor( fabs( fVal ) ), 2.0 ) < 0.5 ); + return bRes; +} + +void ScInterpreter::ScIsEven() +{ + PushInt( int(IsEven()) ); +} + +void ScInterpreter::ScIsOdd() +{ + PushInt( int(!IsEven()) ); +} + +void ScInterpreter::ScN() +{ + FormulaError nErr = nGlobalError; + nGlobalError = FormulaError::NONE; + // Temporarily override the ConvertStringToValue() error for + // GetCellValue() / GetCellValueOrZero() + FormulaError nSErr = mnStringNoValueError; + mnStringNoValueError = FormulaError::CellNoValue; + double fVal = GetDouble(); + mnStringNoValueError = nSErr; + if (nErr != FormulaError::NONE) + nGlobalError = nErr; // preserve previous error if any + else if (nGlobalError == FormulaError::CellNoValue) + nGlobalError = FormulaError::NONE; // reset temporary detection error + PushDouble(fVal); +} + +void ScInterpreter::ScTrim() +{ + // Doesn't only trim but also removes duplicated blanks within! + OUString aVal = comphelper::string::strip(GetString().getString(), ' '); + OUStringBuffer aStr; + const sal_Unicode* p = aVal.getStr(); + const sal_Unicode* const pEnd = p + aVal.getLength(); + while ( p < pEnd ) + { + if ( *p != ' ' || p[-1] != ' ' ) // first can't be ' ', so -1 is fine + aStr.append(*p); + p++; + } + PushString(aStr.makeStringAndClear()); +} + +void ScInterpreter::ScUpper() +{ + OUString aString = ScGlobal::getCharClass().uppercase(GetString().getString()); + PushString(aString); +} + +void ScInterpreter::ScProper() +{ +//2do: what to do with I18N-CJK ?!? + OUStringBuffer aStr(GetString().getString()); + const sal_Int32 nLen = aStr.getLength(); + if ( nLen > 0 ) + { + OUString aUpr(ScGlobal::getCharClass().uppercase(aStr.toString())); + OUString aLwr(ScGlobal::getCharClass().lowercase(aStr.toString())); + aStr[0] = aUpr[0]; + sal_Int32 nPos = 1; + while( nPos < nLen ) + { + OUString aTmpStr( aStr[nPos-1] ); + if ( !ScGlobal::getCharClass().isLetter( aTmpStr, 0 ) ) + aStr[nPos] = aUpr[nPos]; + else + aStr[nPos] = aLwr[nPos]; + ++nPos; + } + } + PushString(aStr.makeStringAndClear()); +} + +void ScInterpreter::ScLower() +{ + OUString aString = ScGlobal::getCharClass().lowercase(GetString().getString()); + PushString(aString); +} + +void ScInterpreter::ScLen() +{ + OUString aStr = GetString().getString(); + sal_Int32 nIdx = 0; + sal_Int32 nCnt = 0; + while ( nIdx < aStr.getLength() ) + { + aStr.iterateCodePoints( &nIdx ); + ++nCnt; + } + PushDouble( nCnt ); +} + +void ScInterpreter::ScT() +{ + switch ( GetStackType() ) + { + case svDoubleRef : + case svSingleRef : + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + { + PushInt(0); + return ; + } + bool bValue = false; + ScRefCellValue aCell(mrDoc, aAdr); + if (GetCellErrCode(aCell) == FormulaError::NONE) + { + switch (aCell.getType()) + { + case CELLTYPE_VALUE : + bValue = true; + break; + case CELLTYPE_FORMULA : + bValue = aCell.getFormula()->IsValue(); + break; + default: + ; // nothing + } + } + if ( bValue ) + PushString(OUString()); + else + { + // like GetString() + svl::SharedString aStr; + GetCellString(aStr, aCell); + PushString(aStr); + } + } + break; + case svMatrix: + case svExternalSingleRef: + case svExternalDoubleRef: + { + double fVal; + svl::SharedString aStr; + ScMatValType nMatValType = GetDoubleOrStringFromMatrix( fVal, aStr); + if (ScMatrix::IsValueType( nMatValType)) + PushString(svl::SharedString::getEmptyString()); + else + PushString( aStr); + } + break; + case svDouble : + { + PopError(); + PushString( OUString() ); + } + break; + case svString : + ; // leave on stack + break; + default : + PushError( FormulaError::UnknownOpCode); + } +} + +void ScInterpreter::ScValue() +{ + OUString aInputString; + double fVal; + + switch ( GetRawStackType() ) + { + case svMissing: + case svEmptyCell: + Pop(); + PushInt(0); + return; + case svDouble: + return; // leave on stack + + case svSingleRef: + case svDoubleRef: + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + { + PushInt(0); + return; + } + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasString()) + { + svl::SharedString aSS; + GetCellString(aSS, aCell); + aInputString = aSS.getString(); + } + else if (aCell.hasNumeric()) + { + PushDouble( GetCellValue(aAdr, aCell) ); + return; + } + else + { + PushDouble(0.0); + return; + } + } + break; + case svMatrix: + { + svl::SharedString aSS; + ScMatValType nType = GetDoubleOrStringFromMatrix( fVal, + aSS); + aInputString = aSS.getString(); + switch (nType) + { + case ScMatValType::Empty: + fVal = 0.0; + [[fallthrough]]; + case ScMatValType::Value: + case ScMatValType::Boolean: + PushDouble( fVal); + return; + case ScMatValType::String: + // evaluated below + break; + default: + PushIllegalArgument(); + } + } + break; + default: + aInputString = GetString().getString(); + break; + } + + sal_uInt32 nFIndex = 0; // 0 for default locale + if (pFormatter->IsNumberFormat(aInputString, nFIndex, fVal)) + PushDouble(fVal); + else + PushIllegalArgument(); +} + +// fdo#57180 +void ScInterpreter::ScNumberValue() +{ + + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 3 ) ) + return; + + OUString aInputString; + OUString aGroupSeparator; + sal_Unicode cDecimalSeparator = 0; + + if ( nParamCount == 3 ) + aGroupSeparator = GetString().getString(); + + if ( nParamCount >= 2 ) + { + OUString aDecimalSeparator = GetString().getString(); + if ( aDecimalSeparator.getLength() == 1 ) + cDecimalSeparator = aDecimalSeparator[ 0 ]; + else + { + PushIllegalArgument(); //if given, separator length must be 1 + return; + } + } + + if ( cDecimalSeparator && aGroupSeparator.indexOf( cDecimalSeparator ) != -1 ) + { + PushIllegalArgument(); //decimal separator cannot appear in group separator + return; + } + + switch (GetStackType()) + { + case svDouble: + return; // leave on stack + default: + aInputString = GetString().getString(); + } + if ( nGlobalError != FormulaError::NONE ) + { + PushError( nGlobalError ); + return; + } + if ( aInputString.isEmpty() ) + { + if ( maCalcConfig.mbEmptyStringAsZero ) + PushDouble( 0.0 ); + else + PushNoValue(); + return; + } + + sal_Int32 nDecSep = aInputString.indexOf( cDecimalSeparator ); + if ( nDecSep != 0 ) + { + OUString aTemporary( nDecSep >= 0 ? aInputString.copy( 0, nDecSep ) : aInputString ); + sal_Int32 nIndex = 0; + while (nIndex < aGroupSeparator.getLength()) + { + sal_uInt32 nChar = aGroupSeparator.iterateCodePoints( &nIndex ); + aTemporary = aTemporary.replaceAll( OUString( &nChar, 1 ), "" ); + } + if ( nDecSep >= 0 ) + aInputString = aTemporary + aInputString.subView( nDecSep ); + else + aInputString = aTemporary; + } + + for ( sal_Int32 i = aInputString.getLength(); --i >= 0; ) + { + sal_Unicode c = aInputString[ i ]; + if ( c == 0x0020 || c == 0x0009 || c == 0x000A || c == 0x000D ) + aInputString = aInputString.replaceAt( i, 1, u"" ); // remove spaces etc. + } + sal_Int32 nPercentCount = 0; + for ( sal_Int32 i = aInputString.getLength() - 1; i >= 0 && aInputString[ i ] == 0x0025; i-- ) + { + aInputString = aInputString.replaceAt( i, 1, u"" ); // remove and count trailing '%' + nPercentCount++; + } + + rtl_math_ConversionStatus eStatus; + sal_Int32 nParseEnd; + double fVal = ::rtl::math::stringToDouble( aInputString, cDecimalSeparator, 0, &eStatus, &nParseEnd ); + if ( eStatus == rtl_math_ConversionStatus_Ok && nParseEnd == aInputString.getLength() ) + { + if (nPercentCount) + fVal *= pow( 10.0, -(nPercentCount * 2)); // process '%' from input string + PushDouble(fVal); + return; + } + PushNoValue(); +} + +static bool lcl_ScInterpreter_IsPrintable( sal_uInt32 nCodePoint ) +{ + return ( !u_isISOControl(nCodePoint) /*not in Cc*/ + && u_isdefined(nCodePoint) /*not in Cn*/ ); +} + + +void ScInterpreter::ScClean() +{ + OUString aStr = GetString().getString(); + + OUStringBuffer aBuf( aStr.getLength() ); + sal_Int32 nIdx = 0; + while ( nIdx < aStr.getLength() ) + { + sal_uInt32 c = aStr.iterateCodePoints( &nIdx ); + if ( lcl_ScInterpreter_IsPrintable( c ) ) + aBuf.appendUtf32( c ); + } + PushString( aBuf.makeStringAndClear() ); +} + + +void ScInterpreter::ScCode() +{ +//2do: make it full range unicode? + OUString aStr = GetString().getString(); + if (aStr.isEmpty()) + PushInt(0); + else + { + //"classic" ByteString conversion flags + const sal_uInt32 convertFlags = + RTL_UNICODETOTEXT_FLAGS_NONSPACING_IGNORE | + RTL_UNICODETOTEXT_FLAGS_CONTROL_IGNORE | + RTL_UNICODETOTEXT_FLAGS_FLUSH | + RTL_UNICODETOTEXT_FLAGS_UNDEFINED_DEFAULT | + RTL_UNICODETOTEXT_FLAGS_INVALID_DEFAULT | + RTL_UNICODETOTEXT_FLAGS_UNDEFINED_REPLACE; + PushInt( static_cast<unsigned char>(OUStringToOString(OUStringChar(aStr[0]), osl_getThreadTextEncoding(), convertFlags).toChar()) ); + } +} + +void ScInterpreter::ScChar() +{ +//2do: make it full range unicode? + double fVal = GetDouble(); + if (fVal < 0.0 || fVal >= 256.0) + PushIllegalArgument(); + else + { + //"classic" ByteString conversion flags + const sal_uInt32 convertFlags = + RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_DEFAULT | + RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_DEFAULT | + RTL_TEXTTOUNICODE_FLAGS_INVALID_DEFAULT; + + char cEncodedChar = static_cast<char>(fVal); + OUString aStr(&cEncodedChar, 1, osl_getThreadTextEncoding(), convertFlags); + PushString(aStr); + } +} + +/* #i70213# fullwidth/halfwidth conversion provided by + * Takashi Nakamoto <bluedwarf@ooo> + * erAck: added Excel compatibility conversions as seen in issue's test case. */ + +static OUString lcl_convertIntoHalfWidth( const OUString & rStr ) +{ + // Make the initialization thread-safe. Since another function needs to be called, move it all to another + // function and thread-safely initialize a static reference in this function. + auto init = []() -> utl::TransliterationWrapper& + { + static utl::TransliterationWrapper trans( ::comphelper::getProcessComponentContext(), TransliterationFlags::NONE ); + trans.loadModuleByImplName( "FULLWIDTH_HALFWIDTH_LIKE_ASC", LANGUAGE_SYSTEM ); + return trans; + }; + static utl::TransliterationWrapper& aTrans( init()); + return aTrans.transliterate( rStr, 0, sal_uInt16( rStr.getLength() ) ); +} + +static OUString lcl_convertIntoFullWidth( const OUString & rStr ) +{ + auto init = []() -> utl::TransliterationWrapper& + { + static utl::TransliterationWrapper trans( ::comphelper::getProcessComponentContext(), TransliterationFlags::NONE ); + trans.loadModuleByImplName( "HALFWIDTH_FULLWIDTH_LIKE_JIS", LANGUAGE_SYSTEM ); + return trans; + }; + static utl::TransliterationWrapper& aTrans( init()); + return aTrans.transliterate( rStr, 0, sal_uInt16( rStr.getLength() ) ); +} + +/* ODFF: + * Summary: Converts half-width to full-width ASCII and katakana characters. + * Semantics: Conversion is done for half-width ASCII and katakana characters, + * other characters are simply copied from T to the result. This is the + * complementary function to ASC. + * For references regarding halfwidth and fullwidth characters see + * http://www.unicode.org/reports/tr11/ + * http://www.unicode.org/charts/charindex2.html#H + * http://www.unicode.org/charts/charindex2.html#F + */ +void ScInterpreter::ScJis() +{ + if (MustHaveParamCount( GetByte(), 1)) + PushString( lcl_convertIntoFullWidth( GetString().getString())); +} + +/* ODFF: + * Summary: Converts full-width to half-width ASCII and katakana characters. + * Semantics: Conversion is done for full-width ASCII and katakana characters, + * other characters are simply copied from T to the result. This is the + * complementary function to JIS. + */ +void ScInterpreter::ScAsc() +{ + if (MustHaveParamCount( GetByte(), 1)) + PushString( lcl_convertIntoHalfWidth( GetString().getString())); +} + +void ScInterpreter::ScUnicode() +{ + if ( MustHaveParamCount( GetByte(), 1 ) ) + { + OUString aStr = GetString().getString(); + if (aStr.isEmpty()) + PushIllegalParameter(); + else + { + PushDouble(aStr.iterateCodePoints(&o3tl::temporary(sal_Int32(0)))); + } + } +} + +void ScInterpreter::ScUnichar() +{ + if ( MustHaveParamCount( GetByte(), 1 ) ) + { + sal_uInt32 nCodePoint = GetUInt32(); + if (nGlobalError != FormulaError::NONE || !rtl::isUnicodeCodePoint(nCodePoint)) + PushIllegalArgument(); + else + { + OUString aStr( &nCodePoint, 1 ); + PushString( aStr ); + } + } +} + +bool ScInterpreter::SwitchToArrayRefList( ScMatrixRef& xResMat, SCSIZE nMatRows, double fCurrent, + const std::function<void( SCSIZE i, double fCurrent )>& MatOpFunc, bool bDoMatOp ) +{ + const ScRefListToken* p = dynamic_cast<const ScRefListToken*>(pStack[sp-1]); + if (!p || !p->IsArrayResult()) + return false; + + if (!xResMat) + { + // Create and init all elements with current value. + assert(nMatRows > 0); + xResMat = GetNewMat( 1, nMatRows, true); + xResMat->FillDouble( fCurrent, 0,0, 0,nMatRows-1); + } + else if (bDoMatOp) + { + // Current value and values from vector are operands + // for each vector position. + for (SCSIZE i=0; i < nMatRows; ++i) + { + MatOpFunc( i, fCurrent); + } + } + return true; +} + +void ScInterpreter::ScMin( bool bTextAsZero ) +{ + short nParamCount = GetByte(); + if (!MustHaveParamCountMin( nParamCount, 1)) + return; + + ScMatrixRef xResMat; + double nMin = ::std::numeric_limits<double>::max(); + auto MatOpFunc = [&xResMat]( SCSIZE i, double fCurMin ) + { + double fVecRes = xResMat->GetDouble(0,i); + if (fVecRes > fCurMin) + xResMat->PutDouble( fCurMin, 0,i); + }; + const SCSIZE nMatRows = GetRefListArrayMaxSize( nParamCount); + size_t nRefArrayPos = std::numeric_limits<size_t>::max(); + + double nVal = 0.0; + ScAddress aAdr; + ScRange aRange; + size_t nRefInList = 0; + while (nParamCount-- > 0) + { + switch (GetStackType()) + { + case svDouble : + { + nVal = GetDouble(); + if (nMin > nVal) nMin = nVal; + nFuncFmtType = SvNumFormatType::NUMBER; + } + break; + case svSingleRef : + { + PopSingleRef( aAdr ); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + { + nVal = GetCellValue(aAdr, aCell); + CurFmtToFuncFmt(); + if (nMin > nVal) nMin = nVal; + } + else if (bTextAsZero && aCell.hasString()) + { + if ( nMin > 0.0 ) + nMin = 0.0; + } + } + break; + case svRefList : + { + // bDoMatOp only for non-array value when switching to + // ArrayRefList. + if (SwitchToArrayRefList( xResMat, nMatRows, nMin, MatOpFunc, + nRefArrayPos == std::numeric_limits<size_t>::max())) + { + nRefArrayPos = nRefInList; + } + } + [[fallthrough]]; + case svDoubleRef : + { + FormulaError nErr = FormulaError::NONE; + PopDoubleRef( aRange, nParamCount, nRefInList); + ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags, bTextAsZero ); + if (aValIter.GetFirst(nVal, nErr)) + { + if (nMin > nVal) + nMin = nVal; + aValIter.GetCurNumFmtInfo( nFuncFmtType, nFuncFmtIndex ); + while ((nErr == FormulaError::NONE) && aValIter.GetNext(nVal, nErr)) + { + if (nMin > nVal) + nMin = nVal; + } + SetError(nErr); + } + if (nRefArrayPos != std::numeric_limits<size_t>::max()) + { + // Update vector element with current value. + MatOpFunc( nRefArrayPos, nMin); + + // Reset. + nMin = std::numeric_limits<double>::max(); + nVal = 0.0; + nRefArrayPos = std::numeric_limits<size_t>::max(); + } + } + break; + case svMatrix : + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatrixRef pMat = GetMatrix(); + if (pMat) + { + nFuncFmtType = SvNumFormatType::NUMBER; + nVal = pMat->GetMinValue(bTextAsZero, bool(mnSubTotalFlags & SubtotalFlags::IgnoreErrVal)); + if (nMin > nVal) + nMin = nVal; + } + } + break; + case svString : + { + Pop(); + if ( bTextAsZero ) + { + if ( nMin > 0.0 ) + nMin = 0.0; + } + else + SetError(FormulaError::IllegalParameter); + } + break; + default : + PopError(); + SetError(FormulaError::IllegalParameter); + } + } + + if (xResMat) + { + // Include value of last non-references-array type and calculate final result. + if (nMin < std::numeric_limits<double>::max()) + { + for (SCSIZE i=0; i < nMatRows; ++i) + { + MatOpFunc( i, nMin); + } + } + else + { + /* TODO: the awkward "no value is minimum 0.0" is likely the case + * if a value is numeric_limits::max. Still, that could be a valid + * minimum value as well, but nVal and nMin had been reset after + * the last svRefList... so we may lie here. */ + for (SCSIZE i=0; i < nMatRows; ++i) + { + double fVecRes = xResMat->GetDouble(0,i); + if (fVecRes == std::numeric_limits<double>::max()) + xResMat->PutDouble( 0.0, 0,i); + } + } + PushMatrix( xResMat); + } + else + { + if (!std::isfinite(nVal)) + PushError( GetDoubleErrorValue( nVal)); + else if ( nVal < nMin ) + PushDouble(0.0); // zero or only empty arguments + else + PushDouble(nMin); + } +} + +void ScInterpreter::ScMax( bool bTextAsZero ) +{ + short nParamCount = GetByte(); + if (!MustHaveParamCountMin( nParamCount, 1)) + return; + + ScMatrixRef xResMat; + double nMax = std::numeric_limits<double>::lowest(); + auto MatOpFunc = [&xResMat]( SCSIZE i, double fCurMax ) + { + double fVecRes = xResMat->GetDouble(0,i); + if (fVecRes < fCurMax) + xResMat->PutDouble( fCurMax, 0,i); + }; + const SCSIZE nMatRows = GetRefListArrayMaxSize( nParamCount); + size_t nRefArrayPos = std::numeric_limits<size_t>::max(); + + double nVal = 0.0; + ScAddress aAdr; + ScRange aRange; + size_t nRefInList = 0; + while (nParamCount-- > 0) + { + switch (GetStackType()) + { + case svDouble : + { + nVal = GetDouble(); + if (nMax < nVal) nMax = nVal; + nFuncFmtType = SvNumFormatType::NUMBER; + } + break; + case svSingleRef : + { + PopSingleRef( aAdr ); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + { + nVal = GetCellValue(aAdr, aCell); + CurFmtToFuncFmt(); + if (nMax < nVal) nMax = nVal; + } + else if (bTextAsZero && aCell.hasString()) + { + if ( nMax < 0.0 ) + nMax = 0.0; + } + } + break; + case svRefList : + { + // bDoMatOp only for non-array value when switching to + // ArrayRefList. + if (SwitchToArrayRefList( xResMat, nMatRows, nMax, MatOpFunc, + nRefArrayPos == std::numeric_limits<size_t>::max())) + { + nRefArrayPos = nRefInList; + } + } + [[fallthrough]]; + case svDoubleRef : + { + FormulaError nErr = FormulaError::NONE; + PopDoubleRef( aRange, nParamCount, nRefInList); + ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags, bTextAsZero ); + if (aValIter.GetFirst(nVal, nErr)) + { + if (nMax < nVal) + nMax = nVal; + aValIter.GetCurNumFmtInfo( nFuncFmtType, nFuncFmtIndex ); + while ((nErr == FormulaError::NONE) && aValIter.GetNext(nVal, nErr)) + { + if (nMax < nVal) + nMax = nVal; + } + SetError(nErr); + } + if (nRefArrayPos != std::numeric_limits<size_t>::max()) + { + // Update vector element with current value. + MatOpFunc( nRefArrayPos, nMax); + + // Reset. + nMax = std::numeric_limits<double>::lowest(); + nVal = 0.0; + nRefArrayPos = std::numeric_limits<size_t>::max(); + } + } + break; + case svMatrix : + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatrixRef pMat = GetMatrix(); + if (pMat) + { + nFuncFmtType = SvNumFormatType::NUMBER; + nVal = pMat->GetMaxValue(bTextAsZero, bool(mnSubTotalFlags & SubtotalFlags::IgnoreErrVal)); + if (nMax < nVal) + nMax = nVal; + } + } + break; + case svString : + { + Pop(); + if ( bTextAsZero ) + { + if ( nMax < 0.0 ) + nMax = 0.0; + } + else + SetError(FormulaError::IllegalParameter); + } + break; + default : + PopError(); + SetError(FormulaError::IllegalParameter); + } + } + + if (xResMat) + { + // Include value of last non-references-array type and calculate final result. + if (nMax > std::numeric_limits<double>::lowest()) + { + for (SCSIZE i=0; i < nMatRows; ++i) + { + MatOpFunc( i, nMax); + } + } + else + { + /* TODO: the awkward "no value is maximum 0.0" is likely the case + * if a value is numeric_limits::lowest. Still, that could be a + * valid maximum value as well, but nVal and nMax had been reset + * after the last svRefList... so we may lie here. */ + for (SCSIZE i=0; i < nMatRows; ++i) + { + double fVecRes = xResMat->GetDouble(0,i); + if (fVecRes == -std::numeric_limits<double>::max()) + xResMat->PutDouble( 0.0, 0,i); + } + } + PushMatrix( xResMat); + } + else + { + if (!std::isfinite(nVal)) + PushError( GetDoubleErrorValue( nVal)); + else if ( nVal > nMax ) + PushDouble(0.0); // zero or only empty arguments + else + PushDouble(nMax); + } +} + +void ScInterpreter::GetStVarParams( bool bTextAsZero, double(*VarResult)( double fVal, size_t nValCount ) ) +{ + short nParamCount = GetByte(); + const SCSIZE nMatRows = GetRefListArrayMaxSize( nParamCount); + + struct ArrayRefListValue + { + std::vector<double> mvValues; + KahanSum mfSum; + ArrayRefListValue() = default; + double get() const { return mfSum.get(); } + }; + std::vector<ArrayRefListValue> vArrayValues; + + std::vector<double> values; + KahanSum fSum = 0.0; + double fVal = 0.0; + ScAddress aAdr; + ScRange aRange; + size_t nRefInList = 0; + while (nGlobalError == FormulaError::NONE && nParamCount-- > 0) + { + switch (GetStackType()) + { + case svDouble : + { + fVal = GetDouble(); + if (nGlobalError == FormulaError::NONE) + { + values.push_back(fVal); + fSum += fVal; + } + } + break; + case svSingleRef : + { + PopSingleRef( aAdr ); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + { + fVal = GetCellValue(aAdr, aCell); + if (nGlobalError == FormulaError::NONE) + { + values.push_back(fVal); + fSum += fVal; + } + } + else if (bTextAsZero && aCell.hasString()) + { + values.push_back(0.0); + } + } + break; + case svRefList : + { + const ScRefListToken* p = dynamic_cast<const ScRefListToken*>(pStack[sp-1]); + if (p && p->IsArrayResult()) + { + size_t nRefArrayPos = nRefInList; + if (vArrayValues.empty()) + { + // Create and init all elements with current value. + assert(nMatRows > 0); + vArrayValues.resize(nMatRows); + for (ArrayRefListValue & it : vArrayValues) + { + it.mvValues = values; + it.mfSum = fSum; + } + } + else + { + // Current value and values from vector are operands + // for each vector position. + for (ArrayRefListValue & it : vArrayValues) + { + it.mvValues.insert( it.mvValues.end(), values.begin(), values.end()); + it.mfSum += fSum; + } + } + ArrayRefListValue& rArrayValue = vArrayValues[nRefArrayPos]; + FormulaError nErr = FormulaError::NONE; + PopDoubleRef( aRange, nParamCount, nRefInList); + ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags, bTextAsZero ); + if (aValIter.GetFirst(fVal, nErr)) + { + do + { + rArrayValue.mvValues.push_back(fVal); + rArrayValue.mfSum += fVal; + } + while ((nErr == FormulaError::NONE) && aValIter.GetNext(fVal, nErr)); + } + if ( nErr != FormulaError::NONE ) + { + rArrayValue.mfSum = CreateDoubleError( nErr); + } + // Reset. + std::vector<double>().swap(values); + fSum = 0.0; + break; + } + } + [[fallthrough]]; + case svDoubleRef : + { + FormulaError nErr = FormulaError::NONE; + PopDoubleRef( aRange, nParamCount, nRefInList); + ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags, bTextAsZero ); + if (aValIter.GetFirst(fVal, nErr)) + { + do + { + values.push_back(fVal); + fSum += fVal; + } + while ((nErr == FormulaError::NONE) && aValIter.GetNext(fVal, nErr)); + } + if ( nErr != FormulaError::NONE ) + { + SetError(nErr); + } + } + break; + case svExternalSingleRef : + case svExternalDoubleRef : + case svMatrix : + { + ScMatrixRef pMat = GetMatrix(); + if (pMat) + { + const bool bIgnoreErrVal = bool(mnSubTotalFlags & SubtotalFlags::IgnoreErrVal); + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + for (SCSIZE nMatCol = 0; nMatCol < nC; nMatCol++) + { + for (SCSIZE nMatRow = 0; nMatRow < nR; nMatRow++) + { + if (!pMat->IsStringOrEmpty(nMatCol,nMatRow)) + { + fVal= pMat->GetDouble(nMatCol,nMatRow); + if (nGlobalError == FormulaError::NONE) + { + values.push_back(fVal); + fSum += fVal; + } + else if (bIgnoreErrVal) + nGlobalError = FormulaError::NONE; + } + else if ( bTextAsZero ) + { + values.push_back(0.0); + } + } + } + } + } + break; + case svString : + { + Pop(); + if ( bTextAsZero ) + { + values.push_back(0.0); + } + else + SetError(FormulaError::IllegalParameter); + } + break; + default : + PopError(); + SetError(FormulaError::IllegalParameter); + } + } + + if (!vArrayValues.empty()) + { + // Include value of last non-references-array type and calculate final result. + if (!values.empty()) + { + for (auto & it : vArrayValues) + { + it.mvValues.insert( it.mvValues.end(), values.begin(), values.end()); + it.mfSum += fSum; + } + } + ScMatrixRef xResMat = GetNewMat( 1, nMatRows, true); + for (SCSIZE r=0; r < nMatRows; ++r) + { + ::std::vector<double>::size_type n = vArrayValues[r].mvValues.size(); + if (!n) + xResMat->PutError( FormulaError::DivisionByZero, 0, r); + else + { + ArrayRefListValue& rArrayValue = vArrayValues[r]; + double vSum = 0.0; + const double vMean = rArrayValue.get() / n; + for (::std::vector<double>::size_type i = 0; i < n; i++) + vSum += ::rtl::math::approxSub( rArrayValue.mvValues[i], vMean) * + ::rtl::math::approxSub( rArrayValue.mvValues[i], vMean); + xResMat->PutDouble( VarResult( vSum, n), 0, r); + } + } + PushMatrix( xResMat); + } + else + { + ::std::vector<double>::size_type n = values.size(); + if (!n) + SetError( FormulaError::DivisionByZero); + double vSum = 0.0; + if (nGlobalError == FormulaError::NONE) + { + const double vMean = fSum.get() / n; + for (::std::vector<double>::size_type i = 0; i < n; i++) + vSum += ::rtl::math::approxSub( values[i], vMean) * ::rtl::math::approxSub( values[i], vMean); + } + PushDouble( VarResult( vSum, n)); + } +} + +void ScInterpreter::ScVar( bool bTextAsZero ) +{ + auto VarResult = []( double fVal, size_t nValCount ) + { + if (nValCount <= 1) + return CreateDoubleError( FormulaError::DivisionByZero ); + else + return fVal / (nValCount - 1); + }; + GetStVarParams( bTextAsZero, VarResult ); +} + +void ScInterpreter::ScVarP( bool bTextAsZero ) +{ + auto VarResult = []( double fVal, size_t nValCount ) + { + return sc::div( fVal, nValCount); + }; + GetStVarParams( bTextAsZero, VarResult ); + +} + +void ScInterpreter::ScStDev( bool bTextAsZero ) +{ + auto VarResult = []( double fVal, size_t nValCount ) + { + if (nValCount <= 1) + return CreateDoubleError( FormulaError::DivisionByZero ); + else + return sqrt( fVal / (nValCount - 1)); + }; + GetStVarParams( bTextAsZero, VarResult ); +} + +void ScInterpreter::ScStDevP( bool bTextAsZero ) +{ + auto VarResult = []( double fVal, size_t nValCount ) + { + if (nValCount == 0) + return CreateDoubleError( FormulaError::DivisionByZero ); + else + return sqrt( fVal / nValCount); + }; + GetStVarParams( bTextAsZero, VarResult ); + + /* this was: PushDouble( sqrt( div( nVal, nValCount))); + * + * Besides that the special NAN gets lost in the call through sqrt(), + * unxlngi6.pro then looped back and forth somewhere between div() and + * ::rtl::math::setNan(). Tests showed that + * + * sqrt( div( 1, 0)); + * + * produced a loop, but + * + * double f1 = div( 1, 0); + * sqrt( f1 ); + * + * was fine. There seems to be some compiler optimization problem. It does + * not occur when compiled with debug=t. + */ +} + +void ScInterpreter::ScColumns() +{ + sal_uInt8 nParamCount = GetByte(); + sal_uLong nVal = 0; + SCCOL nCol1; + SCROW nRow1; + SCTAB nTab1; + SCCOL nCol2; + SCROW nRow2; + SCTAB nTab2; + while (nParamCount-- > 0) + { + switch ( GetStackType() ) + { + case svSingleRef: + PopError(); + nVal++; + break; + case svDoubleRef: + PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + nVal += static_cast<sal_uLong>(nTab2 - nTab1 + 1) * + static_cast<sal_uLong>(nCol2 - nCol1 + 1); + break; + case svMatrix: + { + ScMatrixRef pMat = PopMatrix(); + if (pMat) + { + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + nVal += nC; + } + } + break; + case svExternalSingleRef: + PopError(); + nVal++; + break; + case svExternalDoubleRef: + { + sal_uInt16 nFileId; + OUString aTabName; + ScComplexRefData aRef; + PopExternalDoubleRef( nFileId, aTabName, aRef); + ScRange aAbs = aRef.toAbs(mrDoc, aPos); + nVal += static_cast<sal_uLong>(aAbs.aEnd.Tab() - aAbs.aStart.Tab() + 1) * + static_cast<sal_uLong>(aAbs.aEnd.Col() - aAbs.aStart.Col() + 1); + } + break; + default: + PopError(); + SetError(FormulaError::IllegalParameter); + } + } + PushDouble(static_cast<double>(nVal)); +} + +void ScInterpreter::ScRows() +{ + sal_uInt8 nParamCount = GetByte(); + sal_uLong nVal = 0; + SCCOL nCol1; + SCROW nRow1; + SCTAB nTab1; + SCCOL nCol2; + SCROW nRow2; + SCTAB nTab2; + while (nParamCount-- > 0) + { + switch ( GetStackType() ) + { + case svSingleRef: + PopError(); + nVal++; + break; + case svDoubleRef: + PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + nVal += static_cast<sal_uLong>(nTab2 - nTab1 + 1) * + static_cast<sal_uLong>(nRow2 - nRow1 + 1); + break; + case svMatrix: + { + ScMatrixRef pMat = PopMatrix(); + if (pMat) + { + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + nVal += nR; + } + } + break; + case svExternalSingleRef: + PopError(); + nVal++; + break; + case svExternalDoubleRef: + { + sal_uInt16 nFileId; + OUString aTabName; + ScComplexRefData aRef; + PopExternalDoubleRef( nFileId, aTabName, aRef); + ScRange aAbs = aRef.toAbs(mrDoc, aPos); + nVal += static_cast<sal_uLong>(aAbs.aEnd.Tab() - aAbs.aStart.Tab() + 1) * + static_cast<sal_uLong>(aAbs.aEnd.Row() - aAbs.aStart.Row() + 1); + } + break; + default: + PopError(); + SetError(FormulaError::IllegalParameter); + } + } + PushDouble(static_cast<double>(nVal)); +} + +void ScInterpreter::ScSheets() +{ + sal_uInt8 nParamCount = GetByte(); + sal_uLong nVal; + if ( nParamCount == 0 ) + nVal = mrDoc.GetTableCount(); + else + { + nVal = 0; + SCCOL nCol1; + SCROW nRow1; + SCTAB nTab1; + SCCOL nCol2; + SCROW nRow2; + SCTAB nTab2; + while (nGlobalError == FormulaError::NONE && nParamCount-- > 0) + { + switch ( GetStackType() ) + { + case svSingleRef: + case svExternalSingleRef: + PopError(); + nVal++; + break; + case svDoubleRef: + PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + nVal += static_cast<sal_uLong>(nTab2 - nTab1 + 1); + break; + case svExternalDoubleRef: + { + sal_uInt16 nFileId; + OUString aTabName; + ScComplexRefData aRef; + PopExternalDoubleRef( nFileId, aTabName, aRef); + ScRange aAbs = aRef.toAbs(mrDoc, aPos); + nVal += static_cast<sal_uLong>(aAbs.aEnd.Tab() - aAbs.aStart.Tab() + 1); + } + break; + default: + PopError(); + SetError( FormulaError::IllegalParameter ); + } + } + } + PushDouble( static_cast<double>(nVal) ); +} + +void ScInterpreter::ScColumn() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 0, 1 ) ) + return; + + double nVal = 0.0; + if (nParamCount == 0) + { + nVal = aPos.Col() + 1; + if (bMatrixFormula) + { + SCCOL nCols = 0; + SCROW nRows = 0; + if (pMyFormulaCell) + pMyFormulaCell->GetMatColsRows( nCols, nRows); + bool bMayBeScalar; + if (nCols == 0) + { + // Happens if called via ScViewFunc::EnterMatrix() + // ScFormulaCell::GetResultDimensions() as of course a + // matrix result is not available yet. + nCols = 1; + bMayBeScalar = false; + } + else + { + bMayBeScalar = true; + } + if (!bMayBeScalar || nCols != 1 || nRows != 1) + { + ScMatrixRef pResMat = GetNewMat( static_cast<SCSIZE>(nCols), 1, /*bEmpty*/true ); + if (pResMat) + { + for (SCCOL i=0; i < nCols; ++i) + pResMat->PutDouble( nVal + i, static_cast<SCSIZE>(i), 0); + PushMatrix( pResMat); + return; + } + } + } + } + else + { + switch ( GetStackType() ) + { + case svSingleRef : + { + SCCOL nCol1(0); + SCROW nRow1(0); + SCTAB nTab1(0); + PopSingleRef( nCol1, nRow1, nTab1 ); + nVal = static_cast<double>(nCol1 + 1); + } + break; + case svExternalSingleRef : + { + sal_uInt16 nFileId; + OUString aTabName; + ScSingleRefData aRef; + PopExternalSingleRef( nFileId, aTabName, aRef ); + ScAddress aAbsRef = aRef.toAbs(mrDoc, aPos); + nVal = static_cast<double>( aAbsRef.Col() + 1 ); + } + break; + + case svDoubleRef : + case svExternalDoubleRef : + { + SCCOL nCol1; + SCCOL nCol2; + if ( GetStackType() == svDoubleRef ) + { + SCROW nRow1; + SCTAB nTab1; + SCROW nRow2; + SCTAB nTab2; + PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + } + else + { + sal_uInt16 nFileId; + OUString aTabName; + ScComplexRefData aRef; + PopExternalDoubleRef( nFileId, aTabName, aRef ); + ScRange aAbs = aRef.toAbs(mrDoc, aPos); + nCol1 = aAbs.aStart.Col(); + nCol2 = aAbs.aEnd.Col(); + } + if (nCol2 > nCol1) + { + ScMatrixRef pResMat = GetNewMat( + static_cast<SCSIZE>(nCol2-nCol1+1), 1, /*bEmpty*/true); + if (pResMat) + { + for (SCCOL i = nCol1; i <= nCol2; i++) + pResMat->PutDouble(static_cast<double>(i+1), + static_cast<SCSIZE>(i-nCol1), 0); + PushMatrix(pResMat); + return; + } + } + else + nVal = static_cast<double>(nCol1 + 1); + } + break; + default: + SetError( FormulaError::IllegalParameter ); + } + } + PushDouble( nVal ); +} + +void ScInterpreter::ScRow() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 0, 1 ) ) + return; + + double nVal = 0.0; + if (nParamCount == 0) + { + nVal = aPos.Row() + 1; + if (bMatrixFormula) + { + SCCOL nCols = 0; + SCROW nRows = 0; + if (pMyFormulaCell) + pMyFormulaCell->GetMatColsRows( nCols, nRows); + bool bMayBeScalar; + if (nRows == 0) + { + // Happens if called via ScViewFunc::EnterMatrix() + // ScFormulaCell::GetResultDimensions() as of course a + // matrix result is not available yet. + nRows = 1; + bMayBeScalar = false; + } + else + { + bMayBeScalar = true; + } + if (!bMayBeScalar || nCols != 1 || nRows != 1) + { + ScMatrixRef pResMat = GetNewMat( 1, static_cast<SCSIZE>(nRows), /*bEmpty*/true); + if (pResMat) + { + for (SCROW i=0; i < nRows; i++) + pResMat->PutDouble( nVal + i, 0, static_cast<SCSIZE>(i)); + PushMatrix( pResMat); + return; + } + } + } + } + else + { + switch ( GetStackType() ) + { + case svSingleRef : + { + SCCOL nCol1(0); + SCROW nRow1(0); + SCTAB nTab1(0); + PopSingleRef( nCol1, nRow1, nTab1 ); + nVal = static_cast<double>(nRow1 + 1); + } + break; + case svExternalSingleRef : + { + sal_uInt16 nFileId; + OUString aTabName; + ScSingleRefData aRef; + PopExternalSingleRef( nFileId, aTabName, aRef ); + ScAddress aAbsRef = aRef.toAbs(mrDoc, aPos); + nVal = static_cast<double>( aAbsRef.Row() + 1 ); + } + break; + case svDoubleRef : + case svExternalDoubleRef : + { + SCROW nRow1; + SCROW nRow2; + if ( GetStackType() == svDoubleRef ) + { + SCCOL nCol1; + SCTAB nTab1; + SCCOL nCol2; + SCTAB nTab2; + PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + } + else + { + sal_uInt16 nFileId; + OUString aTabName; + ScComplexRefData aRef; + PopExternalDoubleRef( nFileId, aTabName, aRef ); + ScRange aAbs = aRef.toAbs(mrDoc, aPos); + nRow1 = aAbs.aStart.Row(); + nRow2 = aAbs.aEnd.Row(); + } + if (nRow2 > nRow1) + { + ScMatrixRef pResMat = GetNewMat( 1, + static_cast<SCSIZE>(nRow2-nRow1+1), /*bEmpty*/true); + if (pResMat) + { + for (SCROW i = nRow1; i <= nRow2; i++) + pResMat->PutDouble(static_cast<double>(i+1), 0, + static_cast<SCSIZE>(i-nRow1)); + PushMatrix(pResMat); + return; + } + } + else + nVal = static_cast<double>(nRow1 + 1); + } + break; + default: + SetError( FormulaError::IllegalParameter ); + } + } + PushDouble( nVal ); +} + +void ScInterpreter::ScSheet() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 0, 1 ) ) + return; + + SCTAB nVal = 0; + if ( nParamCount == 0 ) + nVal = aPos.Tab() + 1; + else + { + switch ( GetStackType() ) + { + case svString : + { + svl::SharedString aStr = PopString(); + if ( mrDoc.GetTable(aStr.getString(), nVal)) + ++nVal; + else + SetError( FormulaError::IllegalArgument ); + } + break; + case svSingleRef : + { + SCCOL nCol1(0); + SCROW nRow1(0); + SCTAB nTab1(0); + PopSingleRef(nCol1, nRow1, nTab1); + nVal = nTab1 + 1; + } + break; + case svDoubleRef : + { + SCCOL nCol1; + SCROW nRow1; + SCTAB nTab1; + SCCOL nCol2; + SCROW nRow2; + SCTAB nTab2; + PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + nVal = nTab1 + 1; + } + break; + default: + SetError( FormulaError::IllegalParameter ); + } + if ( nGlobalError != FormulaError::NONE ) + nVal = 0; + } + PushDouble( static_cast<double>(nVal) ); +} + +namespace { + +class VectorMatrixAccessor +{ +public: + VectorMatrixAccessor(const ScMatrix& rMat, bool bColVec) : + mrMat(rMat), mbColVec(bColVec) {} + + bool IsEmpty(SCSIZE i) const + { + return mbColVec ? mrMat.IsEmpty(0, i) : mrMat.IsEmpty(i, 0); + } + + bool IsEmptyPath(SCSIZE i) const + { + return mbColVec ? mrMat.IsEmptyPath(0, i) : mrMat.IsEmptyPath(i, 0); + } + + bool IsValue(SCSIZE i) const + { + return mbColVec ? mrMat.IsValue(0, i) : mrMat.IsValue(i, 0); + } + + bool IsStringOrEmpty(SCSIZE i) const + { + return mbColVec ? mrMat.IsStringOrEmpty(0, i) : mrMat.IsStringOrEmpty(i, 0); + } + + double GetDouble(SCSIZE i) const + { + return mbColVec ? mrMat.GetDouble(0, i) : mrMat.GetDouble(i, 0); + } + + OUString GetString(SCSIZE i) const + { + return mbColVec ? mrMat.GetString(0, i).getString() : mrMat.GetString(i, 0).getString(); + } + + SCSIZE GetElementCount() const + { + SCSIZE nC, nR; + mrMat.GetDimensions(nC, nR); + return mbColVec ? nR : nC; + } + +private: + const ScMatrix& mrMat; + bool mbColVec; +}; + +/** returns -1 when the matrix value is smaller than the query value, 0 when + they are equal, and 1 when the matrix value is larger than the query + value. */ +sal_Int32 lcl_CompareMatrix2Query( + SCSIZE i, const VectorMatrixAccessor& rMat, const ScQueryEntry& rEntry) +{ + if (rMat.IsEmpty(i)) + { + /* TODO: in case we introduced query for real empty this would have to + * be changed! */ + return -1; // empty always less than anything else + } + + /* FIXME: what is an empty path (result of IF(false;true_path) in + * comparisons? */ + + bool bByString = rEntry.GetQueryItem().meType == ScQueryEntry::ByString; + if (rMat.IsValue(i)) + { + const double nVal1 = rMat.GetDouble(i); + if (!std::isfinite(nVal1)) + { + // XXX Querying for error values is not required, otherwise we'd + // need to check here. + return 1; // error always greater than numeric or string + } + + if (bByString) + return -1; // numeric always less than string + + const double nVal2 = rEntry.GetQueryItem().mfVal; + // XXX Querying for error values is not required, otherwise we'd need + // to check here and move that check before the bByString check. + if (nVal1 == nVal2) + return 0; + + return nVal1 < nVal2 ? -1 : 1; + } + + if (!bByString) + return 1; // string always greater than numeric + + OUString aStr1 = rMat.GetString(i); + OUString aStr2 = rEntry.GetQueryItem().maString.getString(); + + return ScGlobal::GetCollator().compareString(aStr1, aStr2); // case-insensitive +} + +/** returns the last item with the identical value as the original item + value. */ +void lcl_GetLastMatch( SCSIZE& rIndex, const VectorMatrixAccessor& rMat, + SCSIZE nMatCount) +{ + if (rMat.IsValue(rIndex)) + { + double nVal = rMat.GetDouble(rIndex); + while (rIndex < nMatCount-1 && rMat.IsValue(rIndex+1) && + nVal == rMat.GetDouble(rIndex+1)) + ++rIndex; + } + // Order of IsEmptyPath, IsEmpty, IsStringOrEmpty is significant! + else if (rMat.IsEmptyPath(rIndex)) + { + while (rIndex < nMatCount-1 && rMat.IsEmptyPath(rIndex+1)) + ++rIndex; + } + else if (rMat.IsEmpty(rIndex)) + { + while (rIndex < nMatCount-1 && rMat.IsEmpty(rIndex+1)) + ++rIndex; + } + else if (rMat.IsStringOrEmpty(rIndex)) + { + OUString aStr( rMat.GetString(rIndex)); + while (rIndex < nMatCount-1 && rMat.IsStringOrEmpty(rIndex+1) && + aStr == rMat.GetString(rIndex+1)) + ++rIndex; + } + else + { + OSL_FAIL("lcl_GetLastMatch: unhandled matrix type"); + } +} + +} + +void ScInterpreter::ScMatch() +{ + + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2, 3 ) ) + return; + + double fTyp; + if (nParamCount == 3) + fTyp = GetDouble(); + else + fTyp = 1.0; + SCCOL nCol1 = 0; + SCROW nRow1 = 0; + SCTAB nTab1 = 0; + SCCOL nCol2 = 0; + SCROW nRow2 = 0; + ScMatrixRef pMatSrc = nullptr; + + switch (GetStackType()) + { + case svSingleRef: + PopSingleRef( nCol1, nRow1, nTab1); + nCol2 = nCol1; + nRow2 = nRow1; + break; + case svDoubleRef: + { + SCTAB nTab2 = 0; + PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + if (nTab1 != nTab2 || (nCol1 != nCol2 && nRow1 != nRow2)) + { + PushIllegalParameter(); + return; + } + } + break; + case svMatrix: + case svExternalDoubleRef: + { + if (GetStackType() == svMatrix) + pMatSrc = PopMatrix(); + else + PopExternalDoubleRef(pMatSrc); + + if (!pMatSrc) + { + PushIllegalParameter(); + return; + } + } + break; + default: + PushIllegalParameter(); + return; + } + + if (nGlobalError == FormulaError::NONE) + { + double fVal; + ScQueryParam rParam; + rParam.nCol1 = nCol1; + rParam.nRow1 = nRow1; + rParam.nCol2 = nCol2; + rParam.nTab = nTab1; + const ScComplexRefData* refData = nullptr; + + ScQueryEntry& rEntry = rParam.GetEntry(0); + ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + rEntry.bDoQuery = true; + if (fTyp < 0.0) + rEntry.eOp = SC_GREATER_EQUAL; + else if (fTyp > 0.0) + rEntry.eOp = SC_LESS_EQUAL; + switch ( GetStackType() ) + { + case svDouble: + { + fVal = GetDouble(); + rItem.mfVal = fVal; + rItem.meType = ScQueryEntry::ByValue; + } + break; + case svString: + { + rItem.meType = ScQueryEntry::ByString; + rItem.maString = GetString(); + } + break; + case svDoubleRef : + refData = GetStackDoubleRef(); + [[fallthrough]]; + case svSingleRef : + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + { + PushInt(0); + return ; + } + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + { + fVal = GetCellValue(aAdr, aCell); + rItem.meType = ScQueryEntry::ByValue; + rItem.mfVal = fVal; + } + else + { + GetCellString(rItem.maString, aCell); + rItem.meType = ScQueryEntry::ByString; + } + } + break; + case svExternalSingleRef: + { + ScExternalRefCache::TokenRef pToken; + PopExternalSingleRef(pToken); + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + if (pToken->GetType() == svDouble) + { + rItem.meType = ScQueryEntry::ByValue; + rItem.mfVal = pToken->GetDouble(); + } + else + { + rItem.meType = ScQueryEntry::ByString; + rItem.maString = pToken->GetString(); + } + } + break; + case svExternalDoubleRef: + case svMatrix : + { + svl::SharedString aStr; + ScMatValType nType = GetDoubleOrStringFromMatrix( + rItem.mfVal, aStr); + rItem.maString = aStr; + rItem.meType = ScMatrix::IsNonValueType(nType) ? + ScQueryEntry::ByString : ScQueryEntry::ByValue; + } + break; + default: + { + PushIllegalParameter(); + return; + } + } + if (rItem.meType == ScQueryEntry::ByString) + { + bool bIsVBAMode = mrDoc.IsInVBAMode(); + + if ( bIsVBAMode ) + rParam.eSearchType = utl::SearchParam::SearchType::Wildcard; + else + rParam.eSearchType = DetectSearchType(rEntry.GetQueryItem().maString.getString(), mrDoc); + } + + if (pMatSrc) // The source data is matrix array. + { + SCSIZE nC, nR; + pMatSrc->GetDimensions( nC, nR); + if (nC > 1 && nR > 1) + { + // The source matrix must be a vector. + PushIllegalParameter(); + return; + } + + // Do not propagate errors from matrix while searching. + pMatSrc->SetErrorInterpreter( nullptr); + + SCSIZE nMatCount = (nC == 1) ? nR : nC; + VectorMatrixAccessor aMatAcc(*pMatSrc, nC == 1); + + // simple serial search for equality mode (source data doesn't + // need to be sorted). + + if (rEntry.eOp == SC_EQUAL) + { + for (SCSIZE i = 0; i < nMatCount; ++i) + { + if (lcl_CompareMatrix2Query( i, aMatAcc, rEntry) == 0) + { + PushDouble(i+1); // found ! + return; + } + } + PushNA(); // not found + return; + } + + // binary search for non-equality mode (the source data is + // assumed to be sorted). + + bool bAscOrder = (rEntry.eOp == SC_LESS_EQUAL); + SCSIZE nFirst = 0, nLast = nMatCount-1, nHitIndex = 0; + for (SCSIZE nLen = nLast-nFirst; nLen > 0; nLen = nLast-nFirst) + { + SCSIZE nMid = nFirst + nLen/2; + sal_Int32 nCmp = lcl_CompareMatrix2Query( nMid, aMatAcc, rEntry); + if (nCmp == 0) + { + // exact match. find the last item with the same value. + lcl_GetLastMatch( nMid, aMatAcc, nMatCount); + PushDouble( nMid+1); + return; + } + + if (nLen == 1) // first and last items are next to each other. + { + if (nCmp < 0) + nHitIndex = bAscOrder ? nLast : nFirst; + else + nHitIndex = bAscOrder ? nFirst : nLast; + break; + } + + if (nCmp < 0) + { + if (bAscOrder) + nFirst = nMid; + else + nLast = nMid; + } + else + { + if (bAscOrder) + nLast = nMid; + else + nFirst = nMid; + } + } + + if (nHitIndex == nMatCount-1) // last item + { + sal_Int32 nCmp = lcl_CompareMatrix2Query( nHitIndex, aMatAcc, rEntry); + if ((bAscOrder && nCmp <= 0) || (!bAscOrder && nCmp >= 0)) + { + // either the last item is an exact match or the real + // hit is beyond the last item. + PushDouble( nHitIndex+1); + return; + } + } + + if (nHitIndex > 0) // valid hit must be 2nd item or higher + { + if ( ! ( rItem.meType == ScQueryEntry::ByString && aMatAcc.IsValue( nHitIndex-1 ) ) && + ! ( rItem.meType == ScQueryEntry::ByValue && !aMatAcc.IsValue( nHitIndex-1 ) ) ) + PushDouble( nHitIndex); // non-exact match + else + PushNA(); + return; + } + + PushNA(); + return; + } + + // The source data is cell range. + SCCOLROW nDelta = 0; + if (nCol1 == nCol2) + { // search row in column + rParam.nRow2 = nRow2; + rEntry.nField = nCol1; + ScAddress aResultPos( nCol1, nRow1, nTab1); + if (!LookupQueryWithCache( aResultPos, rParam, refData)) + { + PushNA(); + return; + } + nDelta = aResultPos.Row() - nRow1; + } + else + { // search column in row + SCCOL nC; + rParam.bByRow = false; + rParam.nRow2 = nRow1; + rEntry.nField = nCol1; + ScQueryCellIteratorDirect aCellIter(mrDoc, mrContext, nTab1, rParam, false); + // Advance Entry.nField in Iterator if column changed + aCellIter.SetAdvanceQueryParamEntryField( true ); + if (fTyp == 0.0) + { // EQUAL + if ( aCellIter.GetFirst() ) + nC = aCellIter.GetCol(); + else + { + PushNA(); + return; + } + } + else + { // <= or >= + SCROW nR; + if ( !aCellIter.FindEqualOrSortedLastInRange( nC, nR ) ) + { + PushNA(); + return; + } + } + nDelta = nC - nCol1; + } + PushDouble(static_cast<double>(nDelta + 1)); + } + else + PushIllegalParameter(); +} + +namespace { + +bool isCellContentEmpty( const ScRefCellValue& rCell ) +{ + switch (rCell.getType()) + { + case CELLTYPE_VALUE: + case CELLTYPE_STRING: + case CELLTYPE_EDIT: + return false; + case CELLTYPE_FORMULA: + { + // NOTE: Excel treats ="" in a referenced cell as blank in + // COUNTBLANK() but not in ISBLANK(), which is inconsistent. + // COUNTBLANK() tests the (display) result whereas ISBLANK() tests + // the cell content. + // ODFF allows both for COUNTBLANK(). + // OOo and LibreOffice prior to 4.4 did not treat ="" as blank in + // COUNTBLANK(), we now do for Excel interoperability. + /* TODO: introduce yet another compatibility option? */ + sc::FormulaResultValue aRes = rCell.getFormula()->GetResult(); + if (aRes.meType != sc::FormulaResultValue::String) + return false; + if (!aRes.maString.isEmpty()) + return false; + } + break; + default: + ; + } + + return true; +} + +} + +void ScInterpreter::ScCountEmptyCells() +{ + if ( !MustHaveParamCount( GetByte(), 1 ) ) + return; + + const SCSIZE nMatRows = GetRefListArrayMaxSize(1); + // There's either one RefList and nothing else, or none. + ScMatrixRef xResMat = (nMatRows ? GetNewMat( 1, nMatRows, /*bEmpty*/true ) : nullptr); + sal_uLong nMaxCount = 0, nCount = 0; + switch (GetStackType()) + { + case svSingleRef : + { + nMaxCount = 1; + ScAddress aAdr; + PopSingleRef( aAdr ); + ScRefCellValue aCell(mrDoc, aAdr); + if (!isCellContentEmpty(aCell)) + nCount = 1; + } + break; + case svRefList : + case svDoubleRef : + { + ScRange aRange; + short nParam = 1; + SCSIZE nRefListArrayPos = 0; + size_t nRefInList = 0; + while (nParam-- > 0) + { + nRefListArrayPos = nRefInList; + PopDoubleRef( aRange, nParam, nRefInList); + nMaxCount += + static_cast<sal_uLong>(aRange.aEnd.Row() - aRange.aStart.Row() + 1) * + static_cast<sal_uLong>(aRange.aEnd.Col() - aRange.aStart.Col() + 1) * + static_cast<sal_uLong>(aRange.aEnd.Tab() - aRange.aStart.Tab() + 1); + + ScCellIterator aIter( mrDoc, aRange, mnSubTotalFlags); + for (bool bHas = aIter.first(); bHas; bHas = aIter.next()) + { + const ScRefCellValue& rCell = aIter.getRefCellValue(); + if (!isCellContentEmpty(rCell)) + ++nCount; + } + if (xResMat) + { + xResMat->PutDouble( nMaxCount - nCount, 0, nRefListArrayPos); + nMaxCount = nCount = 0; + } + } + } + break; + case svMatrix: + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatrixRef xMat = GetMatrix(); + if (!xMat) + SetError( FormulaError::IllegalParameter); + else + { + SCSIZE nC, nR; + xMat->GetDimensions( nC, nR); + nMaxCount = nC * nR; + // Numbers (implicit), strings and error values, ignore empty + // strings as those if not entered in an inline array are the + // result of a formula, to be par with a reference to formula + // cell as *visual* blank, see isCellContentEmpty() above. + nCount = xMat->Count( true, true, true); + } + } + break; + default : SetError(FormulaError::IllegalParameter); break; + } + if (xResMat) + PushMatrix( xResMat); + else + PushDouble(nMaxCount - nCount); +} + +void ScInterpreter::IterateParametersIf( ScIterFuncIf eFunc ) +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2, 3 ) ) + return; + + SCCOL nCol3 = 0; + SCROW nRow3 = 0; + SCTAB nTab3 = 0; + + ScMatrixRef pSumExtraMatrix; + bool bSumExtraRange = (nParamCount == 3); + if (bSumExtraRange) + { + // Save only the upperleft cell in case of cell range. The geometry + // of the 3rd parameter is taken from the 1st parameter. + + switch ( GetStackType() ) + { + case svDoubleRef : + { + SCCOL nColJunk = 0; + SCROW nRowJunk = 0; + SCTAB nTabJunk = 0; + PopDoubleRef( nCol3, nRow3, nTab3, nColJunk, nRowJunk, nTabJunk ); + if ( nTabJunk != nTab3 ) + { + PushError( FormulaError::IllegalParameter); + return; + } + } + break; + case svSingleRef : + PopSingleRef( nCol3, nRow3, nTab3 ); + break; + case svMatrix: + pSumExtraMatrix = PopMatrix(); + // nCol3, nRow3, nTab3 remain 0 + break; + case svExternalSingleRef: + { + pSumExtraMatrix = GetNewMat(1,1); + ScExternalRefCache::TokenRef pToken; + PopExternalSingleRef(pToken); + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + + if (pToken->GetType() == svDouble) + pSumExtraMatrix->PutDouble(pToken->GetDouble(), 0, 0); + else + pSumExtraMatrix->PutString(pToken->GetString(), 0, 0); + } + break; + case svExternalDoubleRef: + PopExternalDoubleRef(pSumExtraMatrix); + break; + default: + PushError( FormulaError::IllegalParameter); + return; + } + } + + svl::SharedString aString; + double fVal = 0.0; + bool bIsString = true; + switch ( GetStackType() ) + { + case svDoubleRef : + case svSingleRef : + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + { + PushError( nGlobalError); + return; + } + + ScRefCellValue aCell(mrDoc, aAdr); + switch (aCell.getType()) + { + case CELLTYPE_VALUE : + fVal = GetCellValue(aAdr, aCell); + bIsString = false; + break; + case CELLTYPE_FORMULA : + if (aCell.getFormula()->IsValue()) + { + fVal = GetCellValue(aAdr, aCell); + bIsString = false; + } + else + GetCellString(aString, aCell); + break; + case CELLTYPE_STRING : + case CELLTYPE_EDIT : + GetCellString(aString, aCell); + break; + default: + fVal = 0.0; + bIsString = false; + } + } + break; + case svString: + aString = GetString(); + break; + case svMatrix : + case svExternalDoubleRef: + { + ScMatValType nType = GetDoubleOrStringFromMatrix( fVal, aString); + bIsString = ScMatrix::IsRealStringType( nType); + } + break; + case svExternalSingleRef: + { + ScExternalRefCache::TokenRef pToken; + PopExternalSingleRef(pToken); + if (nGlobalError == FormulaError::NONE) + { + if (pToken->GetType() == svDouble) + { + fVal = pToken->GetDouble(); + bIsString = false; + } + else + aString = pToken->GetString(); + } + } + break; + default: + { + fVal = GetDouble(); + bIsString = false; + } + } + + KahanSum fSum = 0.0; + double fRes = 0.0; + double fCount = 0.0; + short nParam = 1; + const SCSIZE nMatRows = GetRefListArrayMaxSize( nParam); + // There's either one RefList and nothing else, or none. + ScMatrixRef xResMat = (nMatRows ? GetNewMat( 1, nMatRows, /*bEmpty*/true ) : nullptr); + SCSIZE nRefListArrayPos = 0; + size_t nRefInList = 0; + while (nParam-- > 0) + { + SCCOL nCol1 = 0; + SCROW nRow1 = 0; + SCTAB nTab1 = 0; + SCCOL nCol2 = 0; + SCROW nRow2 = 0; + SCTAB nTab2 = 0; + ScMatrixRef pQueryMatrix; + switch ( GetStackType() ) + { + case svRefList : + if (bSumExtraRange) + { + /* TODO: this could resolve if all refs are of the same size */ + SetError( FormulaError::IllegalParameter); + } + else + { + nRefListArrayPos = nRefInList; + ScRange aRange; + PopDoubleRef( aRange, nParam, nRefInList); + aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + } + break; + case svDoubleRef : + PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + break; + case svSingleRef : + PopSingleRef( nCol1, nRow1, nTab1 ); + nCol2 = nCol1; + nRow2 = nRow1; + nTab2 = nTab1; + break; + case svMatrix: + case svExternalSingleRef: + case svExternalDoubleRef: + { + pQueryMatrix = GetMatrix(); + if (!pQueryMatrix) + { + PushError( FormulaError::IllegalParameter); + return; + } + nCol1 = 0; + nRow1 = 0; + nTab1 = 0; + SCSIZE nC, nR; + pQueryMatrix->GetDimensions( nC, nR); + nCol2 = static_cast<SCCOL>(nC - 1); + nRow2 = static_cast<SCROW>(nR - 1); + nTab2 = 0; + } + break; + default: + SetError( FormulaError::IllegalParameter); + } + if ( nTab1 != nTab2 ) + { + SetError( FormulaError::IllegalParameter); + } + + if (bSumExtraRange) + { + // Take the range geometry of the 1st parameter and apply it to + // the 3rd. If parts of the resulting range would point outside + // the sheet, don't complain but silently ignore and simply cut + // them away, this is what Xcl does :-/ + + // For the cut-away part we also don't need to determine the + // criteria match, so shrink the source range accordingly, + // instead of the result range. + SCCOL nColDelta = nCol2 - nCol1; + SCROW nRowDelta = nRow2 - nRow1; + SCCOL nMaxCol; + SCROW nMaxRow; + if (pSumExtraMatrix) + { + SCSIZE nC, nR; + pSumExtraMatrix->GetDimensions( nC, nR); + nMaxCol = static_cast<SCCOL>(nC - 1); + nMaxRow = static_cast<SCROW>(nR - 1); + } + else + { + nMaxCol = mrDoc.MaxCol(); + nMaxRow = mrDoc.MaxRow(); + } + if (nCol3 + nColDelta > nMaxCol) + { + SCCOL nNewDelta = nMaxCol - nCol3; + nCol2 = nCol1 + nNewDelta; + } + + if (nRow3 + nRowDelta > nMaxRow) + { + SCROW nNewDelta = nMaxRow - nRow3; + nRow2 = nRow1 + nNewDelta; + } + } + else + { + nCol3 = nCol1; + nRow3 = nRow1; + nTab3 = nTab1; + } + + if (nGlobalError == FormulaError::NONE) + { + ScQueryParam rParam; + rParam.nRow1 = nRow1; + rParam.nRow2 = nRow2; + + ScQueryEntry& rEntry = rParam.GetEntry(0); + ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + rEntry.bDoQuery = true; + if (!bIsString) + { + rItem.meType = ScQueryEntry::ByValue; + rItem.mfVal = fVal; + rEntry.eOp = SC_EQUAL; + } + else + { + rParam.FillInExcelSyntax(mrDoc.GetSharedStringPool(), aString.getString(), 0, pFormatter); + if (rItem.meType == ScQueryEntry::ByString) + rParam.eSearchType = DetectSearchType(rItem.maString.getString(), mrDoc); + } + ScAddress aAdr; + aAdr.SetTab( nTab3 ); + rParam.nCol1 = nCol1; + rParam.nCol2 = nCol2; + rEntry.nField = nCol1; + SCCOL nColDiff = nCol3 - nCol1; + SCROW nRowDiff = nRow3 - nRow1; + if (pQueryMatrix) + { + // Never case-sensitive. + sc::CompareOptions aOptions( mrDoc, rEntry, rParam.eSearchType); + ScMatrixRef pResultMatrix = QueryMat( pQueryMatrix, aOptions); + if (nGlobalError != FormulaError::NONE || !pResultMatrix) + { + SetError( FormulaError::IllegalParameter); + } + + if (pSumExtraMatrix) + { + for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol) + { + for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow) + { + if (pResultMatrix->IsValue( nCol, nRow) && + pResultMatrix->GetDouble( nCol, nRow)) + { + SCSIZE nC = nCol + nColDiff; + SCSIZE nR = nRow + nRowDiff; + if (pSumExtraMatrix->IsValue( nC, nR)) + { + fVal = pSumExtraMatrix->GetDouble( nC, nR); + ++fCount; + fSum += fVal; + } + } + } + } + } + else + { + for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol) + { + for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow) + { + if (pResultMatrix->GetDouble( nCol, nRow)) + { + aAdr.SetCol( nCol + nColDiff); + aAdr.SetRow( nRow + nRowDiff); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + { + fVal = GetCellValue(aAdr, aCell); + ++fCount; + fSum += fVal; + } + } + } + } + } + } + else + { + ScQueryCellIteratorDirect aCellIter(mrDoc, mrContext, nTab1, rParam, false); + // Increment Entry.nField in iterator when switching to next column. + aCellIter.SetAdvanceQueryParamEntryField( true ); + if ( aCellIter.GetFirst() ) + { + if (pSumExtraMatrix) + { + do + { + SCSIZE nC = aCellIter.GetCol() + nColDiff; + SCSIZE nR = aCellIter.GetRow() + nRowDiff; + if (pSumExtraMatrix->IsValue( nC, nR)) + { + fVal = pSumExtraMatrix->GetDouble( nC, nR); + ++fCount; + fSum += fVal; + } + } while ( aCellIter.GetNext() ); + } + else + { + do + { + aAdr.SetCol( aCellIter.GetCol() + nColDiff); + aAdr.SetRow( aCellIter.GetRow() + nRowDiff); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + { + fVal = GetCellValue(aAdr, aCell); + ++fCount; + fSum += fVal; + } + } while ( aCellIter.GetNext() ); + } + } + } + } + else + { + PushError( FormulaError::IllegalParameter); + return; + } + + switch( eFunc ) + { + case ifSUMIF: fRes = fSum.get(); break; + case ifAVERAGEIF: fRes = div( fSum.get(), fCount ); break; + } + if (xResMat) + { + if (nGlobalError == FormulaError::NONE) + xResMat->PutDouble( fRes, 0, nRefListArrayPos); + else + { + xResMat->PutError( nGlobalError, 0, nRefListArrayPos); + nGlobalError = FormulaError::NONE; + } + fRes = fCount = 0.0; + fSum = 0; + } + } + if (xResMat) + PushMatrix( xResMat); + else + PushDouble( fRes); +} + +void ScInterpreter::ScSumIf() +{ + IterateParametersIf( ifSUMIF); +} + +void ScInterpreter::ScAverageIf() +{ + IterateParametersIf( ifAVERAGEIF); +} + +void ScInterpreter::ScCountIf() +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + svl::SharedString aString; + double fVal = 0.0; + bool bIsString = true; + switch ( GetStackType() ) + { + case svDoubleRef : + case svSingleRef : + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + { + PushInt(0); + return ; + } + ScRefCellValue aCell(mrDoc, aAdr); + switch (aCell.getType()) + { + case CELLTYPE_VALUE : + fVal = GetCellValue(aAdr, aCell); + bIsString = false; + break; + case CELLTYPE_FORMULA : + if (aCell.getFormula()->IsValue()) + { + fVal = GetCellValue(aAdr, aCell); + bIsString = false; + } + else + GetCellString(aString, aCell); + break; + case CELLTYPE_STRING : + case CELLTYPE_EDIT : + GetCellString(aString, aCell); + break; + default: + fVal = 0.0; + bIsString = false; + } + } + break; + case svMatrix: + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatValType nType = GetDoubleOrStringFromMatrix(fVal, aString); + bIsString = ScMatrix::IsRealStringType( nType); + } + break; + case svString: + aString = GetString(); + break; + default: + { + fVal = GetDouble(); + bIsString = false; + } + } + double fCount = 0.0; + short nParam = 1; + const SCSIZE nMatRows = GetRefListArrayMaxSize( nParam); + // There's either one RefList and nothing else, or none. + ScMatrixRef xResMat = (nMatRows ? GetNewMat( 1, nMatRows, /*bEmpty*/true ) : nullptr); + SCSIZE nRefListArrayPos = 0; + size_t nRefInList = 0; + while (nParam-- > 0) + { + SCCOL nCol1 = 0; + SCROW nRow1 = 0; + SCTAB nTab1 = 0; + SCCOL nCol2 = 0; + SCROW nRow2 = 0; + SCTAB nTab2 = 0; + ScMatrixRef pQueryMatrix; + const ScComplexRefData* refData = nullptr; + switch ( GetStackType() ) + { + case svRefList : + nRefListArrayPos = nRefInList; + [[fallthrough]]; + case svDoubleRef : + { + refData = GetStackDoubleRef(nRefInList); + ScRange aRange; + PopDoubleRef( aRange, nParam, nRefInList); + aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + } + break; + case svSingleRef : + PopSingleRef( nCol1, nRow1, nTab1 ); + nCol2 = nCol1; + nRow2 = nRow1; + nTab2 = nTab1; + break; + case svMatrix: + case svExternalSingleRef: + case svExternalDoubleRef: + { + pQueryMatrix = GetMatrix(); + if (!pQueryMatrix) + { + PushIllegalParameter(); + return; + } + nCol1 = 0; + nRow1 = 0; + nTab1 = 0; + SCSIZE nC, nR; + pQueryMatrix->GetDimensions( nC, nR); + nCol2 = static_cast<SCCOL>(nC - 1); + nRow2 = static_cast<SCROW>(nR - 1); + nTab2 = 0; + } + break; + default: + PopError(); // Propagate it further + PushIllegalParameter(); + return ; + } + if ( nTab1 != nTab2 ) + { + PushIllegalParameter(); + return; + } + if (nCol1 > nCol2) + { + PushIllegalParameter(); + return; + } + if (nGlobalError == FormulaError::NONE) + { + ScQueryParam rParam; + rParam.nRow1 = nRow1; + rParam.nRow2 = nRow2; + rParam.nTab = nTab1; + + ScQueryEntry& rEntry = rParam.GetEntry(0); + ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + rEntry.bDoQuery = true; + if (!bIsString) + { + rItem.meType = ScQueryEntry::ByValue; + rItem.mfVal = fVal; + rEntry.eOp = SC_EQUAL; + } + else + { + rParam.FillInExcelSyntax(mrDoc.GetSharedStringPool(), aString.getString(), 0, pFormatter); + if (rItem.meType == ScQueryEntry::ByString) + rParam.eSearchType = DetectSearchType(rItem.maString.getString(), mrDoc); + } + rParam.nCol1 = nCol1; + rParam.nCol2 = nCol2; + rEntry.nField = nCol1; + if (pQueryMatrix) + { + // Never case-sensitive. + sc::CompareOptions aOptions( mrDoc, rEntry, rParam.eSearchType); + ScMatrixRef pResultMatrix = QueryMat( pQueryMatrix, aOptions); + if (nGlobalError != FormulaError::NONE || !pResultMatrix) + { + PushIllegalParameter(); + return; + } + + SCSIZE nSize = pResultMatrix->GetElementCount(); + for (SCSIZE nIndex = 0; nIndex < nSize; ++nIndex) + { + if (pResultMatrix->IsValue( nIndex) && + pResultMatrix->GetDouble( nIndex)) + ++fCount; + } + } + else + { + if(ScCountIfCellIteratorSortedCache::CanBeUsed(mrDoc, rParam, nTab1, pMyFormulaCell, + refData, mrContext)) + { + ScCountIfCellIteratorSortedCache aCellIter(mrDoc, mrContext, nTab1, rParam, false); + fCount += aCellIter.GetCount(); + } + else + { + ScCountIfCellIteratorDirect aCellIter(mrDoc, mrContext, nTab1, rParam, false); + fCount += aCellIter.GetCount(); + } + } + } + else + { + PushIllegalParameter(); + return; + } + if (xResMat) + { + xResMat->PutDouble( fCount, 0, nRefListArrayPos); + fCount = 0.0; + } + } + if (xResMat) + PushMatrix( xResMat); + else + PushDouble(fCount); +} + +void ScInterpreter::IterateParametersIfs( double(*ResultFunc)( const sc::ParamIfsResult& rRes ) ) +{ + sal_uInt8 nParamCount = GetByte(); + sal_uInt8 nQueryCount = nParamCount / 2; + + std::vector<sal_uInt8>& vConditions = mrContext.maConditions; + // vConditions is cached, although it is clear'ed after every cell is interpreted, + // if the SUMIFS/COUNTIFS are part of a matrix formula, then that is not enough because + // with a single InterpretTail() call it results in evaluation of all the cells in the + // matrix formula. + vConditions.clear(); + + // Range-reduce optimization + SCCOL nStartColDiff = 0; + SCCOL nEndColDiff = 0; + SCROW nStartRowDiff = 0; + SCROW nEndRowDiff = 0; + bool bRangeReduce = false; + ScRange aMainRange; + + bool bHasDoubleRefCriteriaRanges = true; + // Do not attempt main-range reduce if any of the criteria-ranges are not double-refs. + // For COUNTIFS queries it's possible to range-reduce too, if the query is not supposed + // to match empty cells (will be checked and undone later if needed), so simply treat + // the first criteria range as the main range for purposes of detecting if this can be done. + for (sal_uInt16 nParamIdx = 2; nParamIdx < nParamCount; nParamIdx += 2 ) + { + const formula::FormulaToken* pCriteriaRangeToken = pStack[ sp-nParamIdx ]; + if (pCriteriaRangeToken->GetType() != svDoubleRef ) + { + bHasDoubleRefCriteriaRanges = false; + break; + } + } + + // Probe the main range token, and try if we can shrink the range without altering results. + const formula::FormulaToken* pMainRangeToken = pStack[ sp-nParamCount ]; + if (pMainRangeToken->GetType() == svDoubleRef && bHasDoubleRefCriteriaRanges) + { + const ScComplexRefData* pRefData = pMainRangeToken->GetDoubleRef(); + if (!pRefData->IsDeleted()) + { + DoubleRefToRange( *pRefData, aMainRange); + if (aMainRange.aStart.Tab() == aMainRange.aEnd.Tab()) + { + // Shrink the range to actual data content. + ScRange aSubRange = aMainRange; + mrDoc.GetDataAreaSubrange(aSubRange); + nStartColDiff = aSubRange.aStart.Col() - aMainRange.aStart.Col(); + nStartRowDiff = aSubRange.aStart.Row() - aMainRange.aStart.Row(); + nEndColDiff = aSubRange.aEnd.Col() - aMainRange.aEnd.Col(); + nEndRowDiff = aSubRange.aEnd.Row() - aMainRange.aEnd.Row(); + bRangeReduce = nStartColDiff || nStartRowDiff || nEndColDiff || nEndRowDiff; + } + } + } + + double fVal = 0.0; + SCCOL nDimensionCols = 0; + SCROW nDimensionRows = 0; + const SCSIZE nRefArrayRows = GetRefListArrayMaxSize( nParamCount); + std::vector<std::vector<sal_uInt8>> vRefArrayConditions; + + while (nParamCount > 1 && nGlobalError == FormulaError::NONE) + { + // take criteria + svl::SharedString aString; + fVal = 0.0; + bool bIsString = true; + switch ( GetStackType() ) + { + case svDoubleRef : + case svSingleRef : + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + { + PushError( nGlobalError); + return; + } + + ScRefCellValue aCell(mrDoc, aAdr); + switch (aCell.getType()) + { + case CELLTYPE_VALUE : + fVal = GetCellValue(aAdr, aCell); + bIsString = false; + break; + case CELLTYPE_FORMULA : + if (aCell.getFormula()->IsValue()) + { + fVal = GetCellValue(aAdr, aCell); + bIsString = false; + } + else + GetCellString(aString, aCell); + break; + case CELLTYPE_STRING : + case CELLTYPE_EDIT : + GetCellString(aString, aCell); + break; + default: + fVal = 0.0; + bIsString = false; + } + } + break; + case svString: + aString = GetString(); + break; + case svMatrix : + case svExternalDoubleRef: + { + ScMatValType nType = GetDoubleOrStringFromMatrix( fVal, aString); + bIsString = ScMatrix::IsRealStringType( nType); + } + break; + case svExternalSingleRef: + { + ScExternalRefCache::TokenRef pToken; + PopExternalSingleRef(pToken); + if (nGlobalError == FormulaError::NONE) + { + if (pToken->GetType() == svDouble) + { + fVal = pToken->GetDouble(); + bIsString = false; + } + else + aString = pToken->GetString(); + } + } + break; + default: + { + fVal = GetDouble(); + bIsString = false; + } + } + + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; // and bail out, no need to evaluate other arguments + } + + // take range + short nParam = nParamCount; + size_t nRefInList = 0; + size_t nRefArrayPos = std::numeric_limits<size_t>::max(); + SCCOL nCol1 = 0; + SCROW nRow1 = 0; + SCTAB nTab1 = 0; + SCCOL nCol2 = 0; + SCROW nRow2 = 0; + SCTAB nTab2 = 0; + ScMatrixRef pQueryMatrix; + while (nParam-- == nParamCount) + { + const ScComplexRefData* refData = nullptr; + switch ( GetStackType() ) + { + case svRefList : + { + const ScRefListToken* p = dynamic_cast<const ScRefListToken*>(pStack[sp-1]); + if (p && p->IsArrayResult()) + { + if (nRefInList == 0) + { + if (vRefArrayConditions.empty()) + vRefArrayConditions.resize( nRefArrayRows); + if (!vConditions.empty()) + { + // Similar to other reference list array + // handling, add/op the current value to + // all array positions. + for (auto & rVec : vRefArrayConditions) + { + if (rVec.empty()) + rVec = vConditions; + else + { + assert(rVec.size() == vConditions.size()); // see dimensions below + for (size_t i=0, n = rVec.size(); i < n; ++i) + { + rVec[i] += vConditions[i]; + } + } + } + // Reset condition results. + std::for_each( vConditions.begin(), vConditions.end(), + [](sal_uInt8 & r){ r = 0.0; } ); + } + } + nRefArrayPos = nRefInList; + } + refData = GetStackDoubleRef(nRefInList); + ScRange aRange; + PopDoubleRef( aRange, nParam, nRefInList); + aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + } + break; + case svDoubleRef : + refData = GetStackDoubleRef(); + PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + break; + case svSingleRef : + PopSingleRef( nCol1, nRow1, nTab1 ); + nCol2 = nCol1; + nRow2 = nRow1; + nTab2 = nTab1; + break; + case svMatrix: + case svExternalSingleRef: + case svExternalDoubleRef: + { + pQueryMatrix = GetMatrix(); + if (!pQueryMatrix) + { + PushError( FormulaError::IllegalParameter); + return; + } + nCol1 = 0; + nRow1 = 0; + nTab1 = 0; + SCSIZE nC, nR; + pQueryMatrix->GetDimensions( nC, nR); + nCol2 = static_cast<SCCOL>(nC - 1); + nRow2 = static_cast<SCROW>(nR - 1); + nTab2 = 0; + } + break; + default: + PushError( FormulaError::IllegalParameter); + return; + } + if ( nTab1 != nTab2 ) + { + PushError( FormulaError::IllegalArgument); + return; + } + + ScQueryParam rParam; + ScQueryEntry& rEntry = rParam.GetEntry(0); + ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + rEntry.bDoQuery = true; + if (!bIsString) + { + rItem.meType = ScQueryEntry::ByValue; + rItem.mfVal = fVal; + rEntry.eOp = SC_EQUAL; + } + else + { + rParam.FillInExcelSyntax(mrDoc.GetSharedStringPool(), aString.getString(), 0, pFormatter); + if (rItem.meType == ScQueryEntry::ByString) + rParam.eSearchType = DetectSearchType(rItem.maString.getString(), mrDoc); + } + + // Undo bRangeReduce if asked to match empty cells for COUNTIFS (which should be rare). + assert(rEntry.GetQueryItems().size() == 1); + const bool isCountIfs = (nParamCount % 2) == 0; + if(isCountIfs && (rEntry.IsQueryByEmpty() || rItem.mbMatchEmpty) && bRangeReduce) + { + bRangeReduce = false; + // All criteria ranges are svDoubleRef's, so only vConditions needs adjusting. + assert(vRefArrayConditions.empty()); + if(!vConditions.empty()) + { + std::vector<sal_uInt8> newConditions; + SCCOL newDimensionCols = nCol2 - nCol1 + 1; + SCROW newDimensionRows = nRow2 - nRow1 + 1; + newConditions.reserve( newDimensionCols * newDimensionRows ); + SCCOL col = nCol1; + for(; col < nCol1 + nStartColDiff; ++col) + newConditions.insert( newConditions.end(), newDimensionRows, 0 ); + for(; col <= nCol2 - nStartColDiff; ++col) + { + newConditions.insert( newConditions.end(), nStartRowDiff, 0 ); + SCCOL oldCol = col - ( nCol1 + nStartColDiff ); + size_t nIndex = oldCol * nDimensionRows; + if (nIndex < vConditions.size()) + { + auto it = vConditions.begin() + nIndex; + newConditions.insert( newConditions.end(), it, it + nDimensionRows ); + } + else + newConditions.insert( newConditions.end(), nDimensionRows, 0 ); + newConditions.insert( newConditions.end(), -nEndRowDiff, 0 ); + } + for(; col <= nCol2; ++col) + newConditions.insert( newConditions.end(), newDimensionRows, 0 ); + assert( newConditions.size() == o3tl::make_unsigned( newDimensionCols * newDimensionRows )); + vConditions = std::move( newConditions ); + nDimensionCols = newDimensionCols; + nDimensionRows = newDimensionRows; + } + } + + if (bRangeReduce) + { + // All reference ranges must be of the same size as the main range. + if( aMainRange.aEnd.Col() - aMainRange.aStart.Col() != nCol2 - nCol1 + || aMainRange.aEnd.Row() - aMainRange.aStart.Row() != nRow2 - nRow1) + { + PushError ( FormulaError::IllegalArgument); + return; + } + nCol1 += nStartColDiff; + nRow1 += nStartRowDiff; + + nCol2 += nEndColDiff; + nRow2 += nEndRowDiff; + } + + // All reference ranges must be of same dimension and size. + if (!nDimensionCols) + nDimensionCols = nCol2 - nCol1 + 1; + if (!nDimensionRows) + nDimensionRows = nRow2 - nRow1 + 1; + if ((nDimensionCols != (nCol2 - nCol1 + 1)) || (nDimensionRows != (nRow2 - nRow1 + 1))) + { + PushError ( FormulaError::IllegalArgument); + return; + } + + // recalculate matrix values + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + + // initialize temporary result matrix + if (vConditions.empty()) + vConditions.resize( nDimensionCols * nDimensionRows, 0); + + rParam.nRow1 = nRow1; + rParam.nRow2 = nRow2; + rParam.nCol1 = nCol1; + rParam.nCol2 = nCol2; + rEntry.nField = nCol1; + SCCOL nColDiff = -nCol1; + SCROW nRowDiff = -nRow1; + if (pQueryMatrix) + { + // Never case-sensitive. + sc::CompareOptions aOptions(mrDoc, rEntry, rParam.eSearchType); + ScMatrixRef pResultMatrix = QueryMat( pQueryMatrix, aOptions); + if (nGlobalError != FormulaError::NONE || !pResultMatrix) + { + PushError( FormulaError::IllegalParameter); + return; + } + + // result matrix is filled with boolean values. + std::vector<double> aResValues; + pResultMatrix->GetDoubleArray(aResValues); + if (vConditions.size() != aResValues.size()) + { + PushError( FormulaError::IllegalParameter); + return; + } + + std::vector<double>::const_iterator itThisRes = aResValues.begin(); + for (auto& rCondition : vConditions) + { + rCondition += *itThisRes; + ++itThisRes; + } + } + else + { + if( ScQueryCellIteratorSortedCache::CanBeUsed( mrDoc, rParam, nTab1, pMyFormulaCell, + refData, mrContext )) + { + ScQueryCellIteratorSortedCache aCellIter(mrDoc, mrContext, nTab1, rParam, false); + // Increment Entry.nField in iterator when switching to next column. + aCellIter.SetAdvanceQueryParamEntryField( true ); + if ( aCellIter.GetFirst() ) + { + do + { + size_t nC = aCellIter.GetCol() + nColDiff; + size_t nR = aCellIter.GetRow() + nRowDiff; + ++vConditions[nC * nDimensionRows + nR]; + } while ( aCellIter.GetNext() ); + } + } + else + { + ScQueryCellIteratorDirect aCellIter(mrDoc, mrContext, nTab1, rParam, false); + // Increment Entry.nField in iterator when switching to next column. + aCellIter.SetAdvanceQueryParamEntryField( true ); + if ( aCellIter.GetFirst() ) + { + do + { + size_t nC = aCellIter.GetCol() + nColDiff; + size_t nR = aCellIter.GetRow() + nRowDiff; + ++vConditions[nC * nDimensionRows + nR]; + } while ( aCellIter.GetNext() ); + } + } + } + if (nRefArrayPos != std::numeric_limits<size_t>::max()) + { + // Apply condition result to reference list array result position. + std::vector<sal_uInt8>& rVec = vRefArrayConditions[nRefArrayPos]; + if (rVec.empty()) + rVec = vConditions; + else + { + assert(rVec.size() == vConditions.size()); // see dimensions above + for (size_t i=0, n = rVec.size(); i < n; ++i) + { + rVec[i] += vConditions[i]; + } + } + // Reset conditions vector. + // When leaving an svRefList this has to be emptied not set to + // 0.0 because it's checked when entering an svRefList. + if (nRefInList == 0) + std::vector<sal_uInt8>().swap( vConditions); + else + std::for_each( vConditions.begin(), vConditions.end(), [](sal_uInt8 & r){ r = 0; } ); + } + } + nParamCount -= 2; + } + + if (!vRefArrayConditions.empty() && !vConditions.empty()) + { + // Add/op the last current value to all array positions. + for (auto & rVec : vRefArrayConditions) + { + if (rVec.empty()) + rVec = vConditions; + else + { + assert(rVec.size() == vConditions.size()); // see dimensions above + for (size_t i=0, n = rVec.size(); i < n; ++i) + { + rVec[i] += vConditions[i]; + } + } + } + } + + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; // bail out + } + + sc::ParamIfsResult aRes; + ScMatrixRef xResMat; + + // main range - only for AVERAGEIFS, SUMIFS, MINIFS and MAXIFS + if (nParamCount == 1) + { + short nParam = nParamCount; + size_t nRefInList = 0; + size_t nRefArrayPos = std::numeric_limits<size_t>::max(); + bool bRefArrayMain = false; + while (nParam-- == nParamCount) + { + SCCOL nMainCol1 = 0; + SCROW nMainRow1 = 0; + SCTAB nMainTab1 = 0; + SCCOL nMainCol2 = 0; + SCROW nMainRow2 = 0; + SCTAB nMainTab2 = 0; + ScMatrixRef pMainMatrix; + switch ( GetStackType() ) + { + case svRefList : + { + const ScRefListToken* p = dynamic_cast<const ScRefListToken*>(pStack[sp-1]); + if (p && p->IsArrayResult()) + { + if (vRefArrayConditions.empty()) + { + // Replicate conditions if there wasn't a + // reference list array for criteria + // evaluation. + vRefArrayConditions.resize( nRefArrayRows); + for (auto & rVec : vRefArrayConditions) + { + rVec = vConditions; + } + } + + bRefArrayMain = true; + nRefArrayPos = nRefInList; + } + ScRange aRange; + PopDoubleRef( aRange, nParam, nRefInList); + aRange.GetVars( nMainCol1, nMainRow1, nMainTab1, nMainCol2, nMainRow2, nMainTab2); + } + break; + case svDoubleRef : + PopDoubleRef( nMainCol1, nMainRow1, nMainTab1, nMainCol2, nMainRow2, nMainTab2 ); + break; + case svSingleRef : + PopSingleRef( nMainCol1, nMainRow1, nMainTab1 ); + nMainCol2 = nMainCol1; + nMainRow2 = nMainRow1; + nMainTab2 = nMainTab1; + break; + case svMatrix: + case svExternalSingleRef: + case svExternalDoubleRef: + { + pMainMatrix = GetMatrix(); + if (!pMainMatrix) + { + PushError( FormulaError::IllegalParameter); + return; + } + nMainCol1 = 0; + nMainRow1 = 0; + nMainTab1 = 0; + SCSIZE nC, nR; + pMainMatrix->GetDimensions( nC, nR); + nMainCol2 = static_cast<SCCOL>(nC - 1); + nMainRow2 = static_cast<SCROW>(nR - 1); + nMainTab2 = 0; + } + break; + // Treat a scalar value as 1x1 matrix. + case svDouble: + pMainMatrix = GetNewMat(1,1); + nMainCol1 = nMainCol2 = 0; + nMainRow1 = nMainRow2 = 0; + nMainTab1 = nMainTab2 = 0; + pMainMatrix->PutDouble( GetDouble(), 0, 0); + break; + case svString: + pMainMatrix = GetNewMat(1,1); + nMainCol1 = nMainCol2 = 0; + nMainRow1 = nMainRow2 = 0; + nMainTab1 = nMainTab2 = 0; + pMainMatrix->PutString( GetString(), 0, 0); + break; + default: + PopError(); + PushError( FormulaError::IllegalParameter); + return; + } + if ( nMainTab1 != nMainTab2 ) + { + PushError( FormulaError::IllegalArgument); + return; + } + + if (bRangeReduce) + { + nMainCol1 += nStartColDiff; + nMainRow1 += nStartRowDiff; + + nMainCol2 += nEndColDiff; + nMainRow2 += nEndRowDiff; + } + + // All reference ranges must be of same dimension and size. + if ((nDimensionCols != (nMainCol2 - nMainCol1 + 1)) || (nDimensionRows != (nMainRow2 - nMainRow1 + 1))) + { + PushError ( FormulaError::IllegalArgument); + return; + } + + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; // bail out + } + + // end-result calculation + + // This gets weird... if conditions were calculated using a + // reference list array but the main calculation range is not a + // reference list array, then the conditions of the array are + // applied to the main range each in turn to form the array result. + + size_t nRefArrayMainPos = (bRefArrayMain ? nRefArrayPos : + (vRefArrayConditions.empty() ? std::numeric_limits<size_t>::max() : 0)); + const bool bAppliedArray = (!bRefArrayMain && nRefArrayMainPos == 0); + + if (nRefArrayMainPos == 0) + xResMat = GetNewMat( 1, nRefArrayRows, /*bEmpty*/true ); + + if (pMainMatrix) + { + std::vector<double> aMainValues; + pMainMatrix->GetDoubleArray(aMainValues, false); // Map empty values to NaN's. + + do + { + if (nRefArrayMainPos < vRefArrayConditions.size()) + vConditions = vRefArrayConditions[nRefArrayMainPos]; + + if (vConditions.size() != aMainValues.size()) + { + PushError( FormulaError::IllegalArgument); + return; + } + + std::vector<sal_uInt8>::const_iterator itRes = vConditions.begin(), itResEnd = vConditions.end(); + std::vector<double>::const_iterator itMain = aMainValues.begin(); + for (; itRes != itResEnd; ++itRes, ++itMain) + { + if (*itRes != nQueryCount) + continue; + + fVal = *itMain; + if (GetDoubleErrorValue(fVal) == FormulaError::ElementNaN) + continue; + + ++aRes.mfCount; + aRes.mfSum += fVal; + if ( aRes.mfMin > fVal ) + aRes.mfMin = fVal; + if ( aRes.mfMax < fVal ) + aRes.mfMax = fVal; + } + if (nRefArrayMainPos != std::numeric_limits<size_t>::max()) + { + xResMat->PutDouble( ResultFunc( aRes), 0, nRefArrayMainPos); + aRes = sc::ParamIfsResult(); + } + } + while (bAppliedArray && ++nRefArrayMainPos < nRefArrayRows); + } + else + { + ScAddress aAdr; + aAdr.SetTab( nMainTab1 ); + do + { + if (nRefArrayMainPos < vRefArrayConditions.size()) + vConditions = vRefArrayConditions[nRefArrayMainPos]; + + SAL_WARN_IF(nDimensionCols && nDimensionRows && vConditions.empty(), "sc", "ScInterpreter::IterateParametersIfs vConditions is empty"); + if (!vConditions.empty()) + { + std::vector<sal_uInt8>::const_iterator itRes = vConditions.begin(); + for (SCCOL nCol = 0; nCol < nDimensionCols; ++nCol) + { + for (SCROW nRow = 0; nRow < nDimensionRows; ++nRow, ++itRes) + { + if (*itRes == nQueryCount) + { + aAdr.SetCol( nCol + nMainCol1); + aAdr.SetRow( nRow + nMainRow1); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + { + fVal = GetCellValue(aAdr, aCell); + ++aRes.mfCount; + aRes.mfSum += fVal; + if ( aRes.mfMin > fVal ) + aRes.mfMin = fVal; + if ( aRes.mfMax < fVal ) + aRes.mfMax = fVal; + } + } + } + } + } + if (nRefArrayMainPos != std::numeric_limits<size_t>::max()) + { + xResMat->PutDouble( ResultFunc( aRes), 0, nRefArrayMainPos); + aRes = sc::ParamIfsResult(); + } + } + while (bAppliedArray && ++nRefArrayMainPos < nRefArrayRows); + } + } + } + else + { + // COUNTIFS only. + if (vRefArrayConditions.empty()) + { + // The code below is this but optimized for most elements not matching. + // for (auto const & rCond : vConditions) + // if (rCond == nQueryCount) + // ++aRes.mfCount; + static_assert(sizeof(vConditions[0]) == 1); + const sal_uInt8* pos = vConditions.data(); + const sal_uInt8* end = pos + vConditions.size(); + for(;;) + { + pos = static_cast< const sal_uInt8* >( memchr( pos, nQueryCount, end - pos )); + if( pos == nullptr ) + break; + ++aRes.mfCount; + ++pos; + } + } + else + { + xResMat = GetNewMat( 1, nRefArrayRows, /*bEmpty*/true ); + for (size_t i=0, n = vRefArrayConditions.size(); i < n; ++i) + { + double fCount = 0.0; + for (auto const & rCond : vRefArrayConditions[i]) + { + if (rCond == nQueryCount) + ++fCount; + } + xResMat->PutDouble( fCount, 0, i); + } + } + } + + if (xResMat) + PushMatrix( xResMat); + else + PushDouble( ResultFunc( aRes)); +} + +void ScInterpreter::ScSumIfs() +{ + // ScMutationGuard aShouldFail(pDok, ScMutationGuardFlags::CORE); + sal_uInt8 nParamCount = GetByte(); + + if (nParamCount < 3 || (nParamCount % 2 != 1)) + { + PushError( FormulaError::ParameterExpected); + return; + } + + auto ResultFunc = []( const sc::ParamIfsResult& rRes ) + { + return rRes.mfSum.get(); + }; + IterateParametersIfs(ResultFunc); +} + +void ScInterpreter::ScAverageIfs() +{ + sal_uInt8 nParamCount = GetByte(); + + if (nParamCount < 3 || (nParamCount % 2 != 1)) + { + PushError( FormulaError::ParameterExpected); + return; + } + + auto ResultFunc = []( const sc::ParamIfsResult& rRes ) + { + return sc::div( rRes.mfSum.get(), rRes.mfCount); + }; + IterateParametersIfs(ResultFunc); +} + +void ScInterpreter::ScCountIfs() +{ + sal_uInt8 nParamCount = GetByte(); + + if (nParamCount < 2 || (nParamCount % 2 != 0)) + { + PushError( FormulaError::ParameterExpected); + return; + } + + auto ResultFunc = []( const sc::ParamIfsResult& rRes ) + { + return rRes.mfCount; + }; + IterateParametersIfs(ResultFunc); +} + +void ScInterpreter::ScMinIfs_MS() +{ + sal_uInt8 nParamCount = GetByte(); + + if (nParamCount < 3 || (nParamCount % 2 != 1)) + { + PushError( FormulaError::ParameterExpected); + return; + } + + auto ResultFunc = []( const sc::ParamIfsResult& rRes ) + { + return (rRes.mfMin < std::numeric_limits<double>::max()) ? rRes.mfMin : 0.0; + }; + IterateParametersIfs(ResultFunc); +} + + +void ScInterpreter::ScMaxIfs_MS() +{ + sal_uInt8 nParamCount = GetByte(); + + if (nParamCount < 3 || (nParamCount % 2 != 1)) + { + PushError( FormulaError::ParameterExpected); + return; + } + + auto ResultFunc = []( const sc::ParamIfsResult& rRes ) + { + return (rRes.mfMax > std::numeric_limits<double>::lowest()) ? rRes.mfMax : 0.0; + }; + IterateParametersIfs(ResultFunc); +} + +void ScInterpreter::ScLookup() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2, 3 ) ) + return ; + + ScMatrixRef pDataMat = nullptr, pResMat = nullptr; + SCCOL nCol1 = 0, nCol2 = 0, nResCol1 = 0, nResCol2 = 0; + SCROW nRow1 = 0, nRow2 = 0, nResRow1 = 0, nResRow2 = 0; + SCTAB nTab1 = 0, nResTab = 0; + SCSIZE nLenMajor = 0; // length of major direction + bool bVertical = true; // whether to lookup vertically or horizontally + + // The third parameter, result array, double, string and reference. + double fResVal = 0.0; + svl::SharedString aResStr; + StackVar eResArrayType = svUnknown; + + if (nParamCount == 3) + { + eResArrayType = GetStackType(); + switch (eResArrayType) + { + case svDoubleRef: + { + SCTAB nTabJunk; + PopDoubleRef(nResCol1, nResRow1, nResTab, + nResCol2, nResRow2, nTabJunk); + if (nResTab != nTabJunk || + ((nResRow2 - nResRow1) > 0 && (nResCol2 - nResCol1) > 0)) + { + // The result array must be a vector. + PushIllegalParameter(); + return; + } + } + break; + case svSingleRef: + PopSingleRef( nResCol1, nResRow1, nResTab); + nResCol2 = nResCol1; + nResRow2 = nResRow1; + break; + case svMatrix: + case svExternalSingleRef: + case svExternalDoubleRef: + { + pResMat = GetMatrix(); + if (!pResMat) + { + PushIllegalParameter(); + return; + } + SCSIZE nC, nR; + pResMat->GetDimensions(nC, nR); + if (nC != 1 && nR != 1) + { + // Result matrix must be a vector. + PushIllegalParameter(); + return; + } + } + break; + case svDouble: + fResVal = GetDouble(); + break; + case svString: + aResStr = GetString(); + break; + default: + PushIllegalParameter(); + return; + } + } + + // For double, string and single reference. + double fDataVal = 0.0; + svl::SharedString aDataStr; + ScAddress aDataAdr; + bool bValueData = false; + + // Get the data-result range and also determine whether this is vertical + // lookup or horizontal lookup. + + StackVar eDataArrayType = GetStackType(); + switch (eDataArrayType) + { + case svDoubleRef: + { + SCTAB nTabJunk; + PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTabJunk); + if (nTab1 != nTabJunk) + { + PushIllegalParameter(); + return; + } + bVertical = (nRow2 - nRow1) >= (nCol2 - nCol1); + nLenMajor = bVertical ? nRow2 - nRow1 + 1 : nCol2 - nCol1 + 1; + } + break; + case svMatrix: + case svExternalSingleRef: + case svExternalDoubleRef: + { + pDataMat = GetMatrix(); + if (!pDataMat) + { + PushIllegalParameter(); + return; + } + + SCSIZE nC, nR; + pDataMat->GetDimensions(nC, nR); + bVertical = (nR >= nC); + nLenMajor = bVertical ? nR : nC; + } + break; + case svDouble: + { + fDataVal = GetDouble(); + bValueData = true; + } + break; + case svString: + { + aDataStr = GetString(); + } + break; + case svSingleRef: + { + PopSingleRef( aDataAdr ); + ScRefCellValue aCell(mrDoc, aDataAdr); + if (aCell.hasEmptyValue()) + { + // Empty cells aren't found anywhere, bail out early. + SetError( FormulaError::NotAvailable); + } + else if (aCell.hasNumeric()) + { + fDataVal = GetCellValue(aDataAdr, aCell); + bValueData = true; + } + else + GetCellString(aDataStr, aCell); + } + break; + default: + SetError( FormulaError::IllegalParameter); + } + + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + + // Get the lookup value. + + ScQueryParam aParam; + ScQueryEntry& rEntry = aParam.GetEntry(0); + if ( !FillEntry(rEntry) ) + return; + + if ( eDataArrayType == svDouble || eDataArrayType == svString || + eDataArrayType == svSingleRef ) + { + // Delta position for a single value is always 0. + + // Found if data <= query, but not if query is string and found data is + // numeric or vice versa. This is how Excel does it but doesn't + // document it. + + bool bFound = false; + ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + + if ( bValueData ) + { + if (rItem.meType == ScQueryEntry::ByString) + bFound = false; + else + bFound = (fDataVal <= rItem.mfVal); + } + else + { + if (rItem.meType != ScQueryEntry::ByString) + bFound = false; + else + bFound = (ScGlobal::GetCollator().compareString(aDataStr.getString(), rItem.maString.getString()) <= 0); + } + + if (!bFound) + { + PushNA(); + return; + } + + if (pResMat) + { + if (pResMat->IsValue( 0, 0 )) + PushDouble(pResMat->GetDouble( 0, 0 )); + else + PushString(pResMat->GetString(0, 0)); + } + else if (nParamCount == 3) + { + switch (eResArrayType) + { + case svDouble: + PushDouble( fResVal ); + break; + case svString: + PushString( aResStr ); + break; + case svDoubleRef: + case svSingleRef: + PushCellResultToken( true, ScAddress( nResCol1, nResRow1, nResTab), nullptr, nullptr); + break; + default: + assert(!"ScInterpreter::ScLookup: unhandled eResArrayType, single value data"); + PushIllegalParameter(); + } + } + else + { + switch (eDataArrayType) + { + case svDouble: + PushDouble( fDataVal ); + break; + case svString: + PushString( aDataStr ); + break; + case svSingleRef: + PushCellResultToken( true, aDataAdr, nullptr, nullptr); + break; + default: + assert(!"ScInterpreter::ScLookup: unhandled eDataArrayType, single value data"); + PushIllegalParameter(); + } + } + return; + } + + // Now, perform the search to compute the delta position (nDelta). + + if (pDataMat) + { + // Data array is given as a matrix. + rEntry.bDoQuery = true; + rEntry.eOp = SC_LESS_EQUAL; + bool bFound = false; + + SCSIZE nC, nR; + pDataMat->GetDimensions(nC, nR); + + // Do not propagate errors from matrix while copying to vector. + pDataMat->SetErrorInterpreter( nullptr); + + // Excel has an undocumented behaviour in that it seems to internally + // sort an interim array (i.e. error values specifically #DIV/0! are + // sorted to the end) or ignore error values that makes these "get last + // non-empty" searches work, e.g. =LOOKUP(2,1/NOT(ISBLANK(A:A)),A:A) + // see tdf#117016 + // Instead of sorting a million entries of which mostly only a bunch of + // rows are filled and moving error values to the end which most are + // already anyway, assume the matrix to be sorted except error values + // and omit the coded DoubleError values. + // Do this only for a numeric matrix (that includes errors coded as + // doubles), which covers the case in question. + /* TODO: it's unclear whether this really matches Excel behaviour in + * all constellations or if there are cases that include unsorted error + * values and thus yield arbitrary binary search results or something + * different or whether there are cases where error values are also + * omitted from mixed numeric/string arrays or if it's not an interim + * matrix but a cell range reference instead. */ + const bool bOmitErrorValues = (eDataArrayType == svMatrix && pDataMat->IsNumeric()); + + // In case of non-vector matrix, only search the first row or column. + ScMatrixRef pDataMat2; + std::vector<SCCOLROW> vIndex; + if (bOmitErrorValues) + { + std::vector<double> vArray; + VectorMatrixAccessor aMatAcc(*pDataMat, bVertical); + const SCSIZE nElements = aMatAcc.GetElementCount(); + for (SCSIZE i=0; i < nElements; ++i) + { + const double fVal = aMatAcc.GetDouble(i); + if (std::isfinite(fVal)) + { + vArray.push_back(fVal); + vIndex.push_back(i); + } + } + if (vArray.empty()) + { + PushNA(); + return; + } + const size_t nElems = vArray.size(); + if (nElems == nElements) + { + // No error value omitted, use as is. + pDataMat2 = pDataMat; + std::vector<SCCOLROW>().swap( vIndex); + } + else + { + nLenMajor = nElems; + if (bVertical) + { + ScMatrixRef pTempMat = GetNewMat( 1, nElems, /*bEmpty*/true ); + pTempMat->PutDoubleVector( vArray, 0, 0); + pDataMat2 = pTempMat; + } + else + { + ScMatrixRef pTempMat = GetNewMat( nElems, 1, /*bEmpty*/true ); + for (size_t i=0; i < nElems; ++i) + pTempMat->PutDouble( vArray[i], i, 0); + pDataMat2 = pTempMat; + } + } + } + else + { + // Just use as is with the VectorMatrixAccessor. + pDataMat2 = pDataMat; + } + + // Do not propagate errors from matrix while searching. + pDataMat2->SetErrorInterpreter( nullptr); + + VectorMatrixAccessor aMatAcc2(*pDataMat2, bVertical); + + // binary search for non-equality mode (the source data is + // assumed to be sorted in ascending order). + + SCCOLROW nDelta = -1; + + SCSIZE nFirst = 0, nLast = nLenMajor-1; //, nHitIndex = 0; + for (SCSIZE nLen = nLast-nFirst; nLen > 0; nLen = nLast-nFirst) + { + SCSIZE nMid = nFirst + nLen/2; + sal_Int32 nCmp = lcl_CompareMatrix2Query( nMid, aMatAcc2, rEntry); + if (nCmp == 0) + { + // exact match. find the last item with the same value. + lcl_GetLastMatch( nMid, aMatAcc2, nLenMajor); + nDelta = nMid; + bFound = true; + break; + } + + if (nLen == 1) // first and last items are next to each other. + { + nDelta = nCmp < 0 ? nLast - 1 : nFirst - 1; + // If already the 1st item is greater there's nothing found. + bFound = (nDelta >= 0); + break; + } + + if (nCmp < 0) + nFirst = nMid; + else + nLast = nMid; + } + + if (nDelta == static_cast<SCCOLROW>(nLenMajor-2)) // last item + { + sal_Int32 nCmp = lcl_CompareMatrix2Query(nDelta+1, aMatAcc2, rEntry); + if (nCmp <= 0) + { + // either the last item is an exact match or the real + // hit is beyond the last item. + nDelta += 1; + bFound = true; + } + } + else if (nDelta > 0) // valid hit must be 2nd item or higher + { + // non-exact match + bFound = true; + } + + // With 0-9 < A-Z, if query is numeric and data found is string, or + // vice versa, the (yet another undocumented) Excel behavior is to + // return #N/A instead. + + if (bFound) + { + if (!vIndex.empty()) + nDelta = vIndex[nDelta]; + + VectorMatrixAccessor aMatAcc(*pDataMat, bVertical); + SCCOLROW i = nDelta; + SCSIZE n = aMatAcc.GetElementCount(); + if (o3tl::make_unsigned(i) >= n) + i = static_cast<SCCOLROW>(n); + bool bByString = rEntry.GetQueryItem().meType == ScQueryEntry::ByString; + if (bByString == aMatAcc.IsValue(i)) + bFound = false; + } + + if (!bFound) + { + PushNA(); + return; + } + + // Now that we've found the delta, push the result back to the cell. + + if (pResMat) + { + VectorMatrixAccessor aResMatAcc(*pResMat, bVertical); + // Result array is matrix. + // Note this does not replicate the other dimension. + if (o3tl::make_unsigned(nDelta) >= aResMatAcc.GetElementCount()) + { + PushNA(); + return; + } + if (aResMatAcc.IsValue(nDelta)) + PushDouble(aResMatAcc.GetDouble(nDelta)); + else + PushString(aResMatAcc.GetString(nDelta)); + } + else if (nParamCount == 3) + { + /* TODO: the entire switch is a copy of the cell range search + * result, factor out. */ + switch (eResArrayType) + { + case svDoubleRef: + case svSingleRef: + { + // Use the result array vector. Note that the result array is assumed + // to be a vector (i.e. 1-dimensional array). + + ScAddress aAdr; + aAdr.SetTab(nResTab); + bool bResVertical = (nResRow2 - nResRow1) > 0; + if (bResVertical) + { + SCROW nTempRow = static_cast<SCROW>(nResRow1 + nDelta); + if (nTempRow > mrDoc.MaxRow()) + { + PushDouble(0); + return; + } + aAdr.SetCol(nResCol1); + aAdr.SetRow(nTempRow); + } + else + { + SCCOL nTempCol = static_cast<SCCOL>(nResCol1 + nDelta); + if (nTempCol > mrDoc.MaxCol()) + { + PushDouble(0); + return; + } + aAdr.SetCol(nTempCol); + aAdr.SetRow(nResRow1); + } + PushCellResultToken( true, aAdr, nullptr, nullptr); + } + break; + case svDouble: + case svString: + { + if (nDelta != 0) + PushNA(); + else + { + switch (eResArrayType) + { + case svDouble: + PushDouble( fResVal ); + break; + case svString: + PushString( aResStr ); + break; + default: + ; // nothing + } + } + } + break; + default: + assert(!"ScInterpreter::ScLookup: unhandled eResArrayType, array search"); + PushIllegalParameter(); + } + } + else + { + // No result array. Use the data array to get the final value from. + // Propagate errors from matrix again. + pDataMat->SetErrorInterpreter( this); + if (bVertical) + { + if (pDataMat->IsValue(nC-1, nDelta)) + PushDouble(pDataMat->GetDouble(nC-1, nDelta)); + else + PushString(pDataMat->GetString(nC-1, nDelta)); + } + else + { + if (pDataMat->IsValue(nDelta, nR-1)) + PushDouble(pDataMat->GetDouble(nDelta, nR-1)); + else + PushString(pDataMat->GetString(nDelta, nR-1)); + } + } + + return; + } + + // Perform cell range search. + + aParam.nCol1 = nCol1; + aParam.nRow1 = nRow1; + aParam.nCol2 = bVertical ? nCol1 : nCol2; + aParam.nRow2 = bVertical ? nRow2 : nRow1; + aParam.bByRow = bVertical; + + rEntry.bDoQuery = true; + rEntry.eOp = SC_LESS_EQUAL; + rEntry.nField = nCol1; + ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + if (rItem.meType == ScQueryEntry::ByString) + aParam.eSearchType = DetectSearchType(rItem.maString.getString(), mrDoc); + + ScQueryCellIteratorDirect aCellIter(mrDoc, mrContext, nTab1, aParam, false); + SCCOL nC; + SCROW nR; + // Advance Entry.nField in iterator upon switching columns if + // lookup in row. + aCellIter.SetAdvanceQueryParamEntryField(!bVertical); + if ( !aCellIter.FindEqualOrSortedLastInRange(nC, nR) ) + { + PushNA(); + return; + } + + SCCOLROW nDelta = bVertical ? static_cast<SCSIZE>(nR-nRow1) : static_cast<SCSIZE>(nC-nCol1); + + if (pResMat) + { + VectorMatrixAccessor aResMatAcc(*pResMat, bVertical); + // Use the matrix result array. + // Note this does not replicate the other dimension. + if (o3tl::make_unsigned(nDelta) >= aResMatAcc.GetElementCount()) + { + PushNA(); + return; + } + if (aResMatAcc.IsValue(nDelta)) + PushDouble(aResMatAcc.GetDouble(nDelta)); + else + PushString(aResMatAcc.GetString(nDelta)); + } + else if (nParamCount == 3) + { + /* TODO: the entire switch is a copy of the array search result, factor + * out. */ + switch (eResArrayType) + { + case svDoubleRef: + case svSingleRef: + { + // Use the result array vector. Note that the result array is assumed + // to be a vector (i.e. 1-dimensional array). + + ScAddress aAdr; + aAdr.SetTab(nResTab); + bool bResVertical = (nResRow2 - nResRow1) > 0; + if (bResVertical) + { + SCROW nTempRow = static_cast<SCROW>(nResRow1 + nDelta); + if (nTempRow > mrDoc.MaxRow()) + { + PushDouble(0); + return; + } + aAdr.SetCol(nResCol1); + aAdr.SetRow(nTempRow); + } + else + { + SCCOL nTempCol = static_cast<SCCOL>(nResCol1 + nDelta); + if (nTempCol > mrDoc.MaxCol()) + { + PushDouble(0); + return; + } + aAdr.SetCol(nTempCol); + aAdr.SetRow(nResRow1); + } + PushCellResultToken( true, aAdr, nullptr, nullptr); + } + break; + case svDouble: + case svString: + { + if (nDelta != 0) + PushNA(); + else + { + switch (eResArrayType) + { + case svDouble: + PushDouble( fResVal ); + break; + case svString: + PushString( aResStr ); + break; + default: + ; // nothing + } + } + } + break; + default: + assert(!"ScInterpreter::ScLookup: unhandled eResArrayType, range search"); + PushIllegalParameter(); + } + } + else + { + // Regardless of whether or not the result array exists, the last + // array is always used as the "result" array. + + ScAddress aAdr; + aAdr.SetTab(nTab1); + if (bVertical) + { + SCROW nTempRow = static_cast<SCROW>(nRow1 + nDelta); + if (nTempRow > mrDoc.MaxRow()) + { + PushDouble(0); + return; + } + aAdr.SetCol(nCol2); + aAdr.SetRow(nTempRow); + } + else + { + SCCOL nTempCol = static_cast<SCCOL>(nCol1 + nDelta); + if (nTempCol > mrDoc.MaxCol()) + { + PushDouble(0); + return; + } + aAdr.SetCol(nTempCol); + aAdr.SetRow(nRow2); + } + PushCellResultToken(true, aAdr, nullptr, nullptr); + } +} + +void ScInterpreter::ScHLookup() +{ + CalculateLookup(true); +} + +void ScInterpreter::CalculateLookup(bool bHLookup) +{ + sal_uInt8 nParamCount = GetByte(); + if (!MustHaveParamCount(nParamCount, 3, 4)) + return; + + // Optional 4th argument to declare whether or not the range is sorted. + bool bSorted = true; + if (nParamCount == 4) + bSorted = GetBool(); + + // Index of column to search. + double fIndex = ::rtl::math::approxFloor( GetDouble() ) - 1.0; + + ScMatrixRef pMat = nullptr; + SCSIZE nC = 0, nR = 0; + SCCOL nCol1 = 0; + SCROW nRow1 = 0; + SCTAB nTab1 = 0; + SCCOL nCol2 = 0; + SCROW nRow2 = 0; + const ScComplexRefData* refData = nullptr; + StackVar eType = GetStackType(); + if (eType == svDoubleRef) + { + refData = GetStackDoubleRef(0); + SCTAB nTab2; + PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + if (nTab1 != nTab2) + { + PushIllegalParameter(); + return; + } + } + else if (eType == svSingleRef) + { + PopSingleRef(nCol1, nRow1, nTab1); + nCol2 = nCol1; + nRow2 = nRow1; + } + else if (eType == svMatrix || eType == svExternalDoubleRef || eType == svExternalSingleRef) + { + pMat = GetMatrix(); + + if (pMat) + pMat->GetDimensions(nC, nR); + else + { + PushIllegalParameter(); + return; + } + } + else + { + PushIllegalParameter(); + return; + } + + if ( fIndex < 0.0 || (bHLookup ? (pMat ? (fIndex >= nR) : (fIndex+nRow1 > nRow2)) : (pMat ? (fIndex >= nC) : (fIndex+nCol1 > nCol2)) ) ) + { + PushIllegalArgument(); + return; + } + + SCROW nZIndex = static_cast<SCROW>(fIndex); + SCCOL nSpIndex = static_cast<SCCOL>(fIndex); + + if (!pMat) + { + nZIndex += nRow1; // value row + nSpIndex = sal::static_int_cast<SCCOL>( nSpIndex + nCol1 ); // value column + } + + if (nGlobalError != FormulaError::NONE) + { + PushIllegalParameter(); + return; + } + + ScQueryParam aParam; + aParam.nCol1 = nCol1; + aParam.nRow1 = nRow1; + if ( bHLookup ) + { + aParam.nCol2 = nCol2; + aParam.nRow2 = nRow1; // search only in the first row + aParam.bByRow = false; + } + else + { + aParam.nCol2 = nCol1; // search only in the first column + aParam.nRow2 = nRow2; + aParam.nTab = nTab1; + } + + ScQueryEntry& rEntry = aParam.GetEntry(0); + rEntry.bDoQuery = true; + if ( bSorted ) + rEntry.eOp = SC_LESS_EQUAL; + if ( !FillEntry(rEntry) ) + return; + + ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + if (rItem.meType == ScQueryEntry::ByString) + aParam.eSearchType = DetectSearchType(rItem.maString.getString(), mrDoc); + if (pMat) + { + SCSIZE nMatCount = bHLookup ? nC : nR; + SCSIZE nDelta = SCSIZE_MAX; + if (rItem.meType == ScQueryEntry::ByString) + { +//!!!!!!! +//TODO: enable regex on matrix strings +//!!!!!!! + svl::SharedString aParamStr = rItem.maString; + if ( bSorted ) + { + CollatorWrapper& rCollator = ScGlobal::GetCollator(); + for (SCSIZE i = 0; i < nMatCount; i++) + { + if (bHLookup ? pMat->IsStringOrEmpty(i, 0) : pMat->IsStringOrEmpty(0, i)) + { + sal_Int32 nRes = + rCollator.compareString( + bHLookup ? pMat->GetString(i,0).getString() : pMat->GetString(0,i).getString(), aParamStr.getString()); + if (nRes <= 0) + nDelta = i; + else if (i>0) // #i2168# ignore first mismatch + i = nMatCount+1; + } + else + nDelta = i; + } + } + else + { + if (bHLookup) + { + for (SCSIZE i = 0; i < nMatCount; i++) + { + if (pMat->IsStringOrEmpty(i, 0)) + { + if (pMat->GetString(i,0).getDataIgnoreCase() == aParamStr.getDataIgnoreCase()) + { + nDelta = i; + i = nMatCount + 1; + } + } + } + } + else + { + nDelta = pMat->MatchStringInColumns(aParamStr, 0, 0); + } + } + } + else + { + if ( bSorted ) + { + // #i2168# ignore strings + for (SCSIZE i = 0; i < nMatCount; i++) + { + if (!(bHLookup ? pMat->IsStringOrEmpty(i, 0) : pMat->IsStringOrEmpty(0, i))) + { + if ((bHLookup ? pMat->GetDouble(i,0) : pMat->GetDouble(0,i)) <= rItem.mfVal) + nDelta = i; + else + i = nMatCount+1; + } + } + } + else + { + if (bHLookup) + { + for (SCSIZE i = 0; i < nMatCount; i++) + { + if (! pMat->IsStringOrEmpty(i, 0) ) + { + if ( pMat->GetDouble(i,0) == rItem.mfVal) + { + nDelta = i; + i = nMatCount + 1; + } + } + } + } + else + { + nDelta = pMat->MatchDoubleInColumns(rItem.mfVal, 0, 0); + } + } + } + if ( nDelta != SCSIZE_MAX ) + { + SCSIZE nX = static_cast<SCSIZE>(nSpIndex); + SCSIZE nY = nDelta; + SCSIZE nXs = 0; + SCSIZE nYs = nY; + if ( bHLookup ) + { + nX = nDelta; + nY = static_cast<SCSIZE>(nZIndex); + nXs = nX; + nYs = 0; + } + assert( nX < nC && nY < nR ); + if (!(rItem.meType == ScQueryEntry::ByString && pMat->IsValue( nXs, nYs))) + { + if (pMat->IsStringOrEmpty( nX, nY)) + PushString(pMat->GetString( nX, nY).getString()); + else + PushDouble(pMat->GetDouble( nX, nY)); + } + else + PushNA(); + return; + } + else + PushNA(); + } + else + { + rEntry.nField = nCol1; + bool bFound = false; + SCCOL nCol = 0; + SCROW nRow = 0; + if ( bSorted ) + rEntry.eOp = SC_LESS_EQUAL; + if ( bHLookup ) + { + ScQueryCellIteratorDirect aCellIter(mrDoc, mrContext, nTab1, aParam, false); + // advance Entry.nField in Iterator upon switching columns + aCellIter.SetAdvanceQueryParamEntryField( true ); + if ( bSorted ) + { + SCROW nRow1_temp; + bFound = aCellIter.FindEqualOrSortedLastInRange( nCol, nRow1_temp ); + } + else if ( aCellIter.GetFirst() ) + { + bFound = true; + nCol = aCellIter.GetCol(); + } + nRow = nZIndex; + } + else + { + ScAddress aResultPos( nCol1, nRow1, nTab1); + bFound = LookupQueryWithCache( aResultPos, aParam, refData); + nRow = aResultPos.Row(); + nCol = nSpIndex; + } + + if ( bFound ) + { + ScAddress aAdr( nCol, nRow, nTab1 ); + PushCellResultToken( true, aAdr, nullptr, nullptr); + } + else + PushNA(); + } +} + +bool ScInterpreter::FillEntry(ScQueryEntry& rEntry) +{ + ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + switch ( GetStackType() ) + { + case svDouble: + { + rItem.meType = ScQueryEntry::ByValue; + rItem.mfVal = GetDouble(); + } + break; + case svString: + { + rItem.meType = ScQueryEntry::ByString; + rItem.maString = GetString(); + } + break; + case svDoubleRef : + case svSingleRef : + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + { + PushInt(0); + return false; + } + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + { + rItem.meType = ScQueryEntry::ByValue; + rItem.mfVal = GetCellValue(aAdr, aCell); + } + else + { + GetCellString(rItem.maString, aCell); + rItem.meType = ScQueryEntry::ByString; + } + } + break; + case svExternalDoubleRef: + case svExternalSingleRef: + case svMatrix: + { + svl::SharedString aStr; + const ScMatValType nType = GetDoubleOrStringFromMatrix(rItem.mfVal, aStr); + rItem.maString = aStr; + rItem.meType = ScMatrix::IsNonValueType(nType) ? + ScQueryEntry::ByString : ScQueryEntry::ByValue; + } + break; + default: + { + PushIllegalParameter(); + return false; + } + } // switch ( GetStackType() ) + return true; +} + +void ScInterpreter::ScVLookup() +{ + CalculateLookup(false); +} + +void ScInterpreter::ScSubTotal() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCountMinWithStackCheck( nParamCount, 2 ) ) + return; + + // We must fish the 1st parameter deep from the stack! And push it on top. + const FormulaToken* p = pStack[ sp - nParamCount ]; + PushWithoutError( *p ); + sal_Int32 nFunc = GetInt32(); + mnSubTotalFlags |= SubtotalFlags::IgnoreNestedStAg | SubtotalFlags::IgnoreFiltered; + if (nFunc > 100) + { + // For opcodes 101 through 111, we need to skip hidden cells. + // Other than that these opcodes are identical to 1 through 11. + mnSubTotalFlags |= SubtotalFlags::IgnoreHidden; + nFunc -= 100; + } + + if ( nGlobalError != FormulaError::NONE || nFunc < 1 || nFunc > 11 ) + PushIllegalArgument(); // simulate return on stack, not SetError(...) + else + { + cPar = nParamCount - 1; + switch( nFunc ) + { + case SUBTOTAL_FUNC_AVE : ScAverage(); break; + case SUBTOTAL_FUNC_CNT : ScCount(); break; + case SUBTOTAL_FUNC_CNT2 : ScCount2(); break; + case SUBTOTAL_FUNC_MAX : ScMax(); break; + case SUBTOTAL_FUNC_MIN : ScMin(); break; + case SUBTOTAL_FUNC_PROD : ScProduct(); break; + case SUBTOTAL_FUNC_STD : ScStDev(); break; + case SUBTOTAL_FUNC_STDP : ScStDevP(); break; + case SUBTOTAL_FUNC_SUM : ScSum(); break; + case SUBTOTAL_FUNC_VAR : ScVar(); break; + case SUBTOTAL_FUNC_VARP : ScVarP(); break; + default : PushIllegalArgument(); break; + } + } + mnSubTotalFlags = SubtotalFlags::NONE; + // Get rid of the 1st (fished) parameter. + FormulaConstTokenRef xRef( PopToken()); + Pop(); + PushTokenRef( xRef); +} + +void ScInterpreter::ScAggregate() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCountMinWithStackCheck( nParamCount, 3 ) ) + return; + + const FormulaError nErr = nGlobalError; + nGlobalError = FormulaError::NONE; + + // fish the 1st parameter from the stack and push it on top. + const FormulaToken* p = pStack[ sp - nParamCount ]; + PushWithoutError( *p ); + sal_Int32 nFunc = GetInt32(); + // fish the 2nd parameter from the stack and push it on top. + const FormulaToken* p2 = pStack[ sp - ( nParamCount - 1 ) ]; + PushWithoutError( *p2 ); + sal_Int32 nOption = GetInt32(); + + if ( nGlobalError != FormulaError::NONE || nFunc < 1 || nFunc > 19 ) + { + nGlobalError = nErr; + PushIllegalArgument(); + } + else + { + switch ( nOption) + { + case 0 : // ignore nested SUBTOTAL and AGGREGATE functions + mnSubTotalFlags = SubtotalFlags::IgnoreNestedStAg; + break; + case 1 : // ignore hidden rows, nested SUBTOTAL and AGGREGATE functions + mnSubTotalFlags = SubtotalFlags::IgnoreHidden | SubtotalFlags::IgnoreNestedStAg; + break; + case 2 : // ignore error values, nested SUBTOTAL and AGGREGATE functions + mnSubTotalFlags = SubtotalFlags::IgnoreErrVal | SubtotalFlags::IgnoreNestedStAg; + break; + case 3 : // ignore hidden rows, error values, nested SUBTOTAL and AGGREGATE functions + mnSubTotalFlags = SubtotalFlags::IgnoreHidden | SubtotalFlags::IgnoreErrVal | SubtotalFlags::IgnoreNestedStAg; + break; + case 4 : // ignore nothing + mnSubTotalFlags = SubtotalFlags::NONE; + break; + case 5 : // ignore hidden rows + mnSubTotalFlags = SubtotalFlags::IgnoreHidden ; + break; + case 6 : // ignore error values + mnSubTotalFlags = SubtotalFlags::IgnoreErrVal ; + break; + case 7 : // ignore hidden rows and error values + mnSubTotalFlags = SubtotalFlags::IgnoreHidden | SubtotalFlags::IgnoreErrVal ; + break; + default : + nGlobalError = nErr; + PushIllegalArgument(); + return; + } + + if ((mnSubTotalFlags & SubtotalFlags::IgnoreErrVal) == SubtotalFlags::NONE) + nGlobalError = nErr; + + cPar = nParamCount - 2; + switch ( nFunc ) + { + case AGGREGATE_FUNC_AVE : ScAverage(); break; + case AGGREGATE_FUNC_CNT : ScCount(); break; + case AGGREGATE_FUNC_CNT2 : ScCount2(); break; + case AGGREGATE_FUNC_MAX : ScMax(); break; + case AGGREGATE_FUNC_MIN : ScMin(); break; + case AGGREGATE_FUNC_PROD : ScProduct(); break; + case AGGREGATE_FUNC_STD : ScStDev(); break; + case AGGREGATE_FUNC_STDP : ScStDevP(); break; + case AGGREGATE_FUNC_SUM : ScSum(); break; + case AGGREGATE_FUNC_VAR : ScVar(); break; + case AGGREGATE_FUNC_VARP : ScVarP(); break; + case AGGREGATE_FUNC_MEDIAN : ScMedian(); break; + case AGGREGATE_FUNC_MODSNGL : ScModalValue(); break; + case AGGREGATE_FUNC_LARGE : ScLarge(); break; + case AGGREGATE_FUNC_SMALL : ScSmall(); break; + case AGGREGATE_FUNC_PERCINC : ScPercentile( true ); break; + case AGGREGATE_FUNC_QRTINC : ScQuartile( true ); break; + case AGGREGATE_FUNC_PERCEXC : ScPercentile( false ); break; + case AGGREGATE_FUNC_QRTEXC : ScQuartile( false ); break; + default: + nGlobalError = nErr; + PushIllegalArgument(); + break; + } + mnSubTotalFlags = SubtotalFlags::NONE; + } + FormulaConstTokenRef xRef( PopToken()); + // Get rid of the 1st and 2nd (fished) parameters. + Pop(); + Pop(); + PushTokenRef( xRef); +} + +std::unique_ptr<ScDBQueryParamBase> ScInterpreter::GetDBParams( bool& rMissingField ) +{ + bool bAllowMissingField = false; + if ( rMissingField ) + { + bAllowMissingField = true; + rMissingField = false; + } + if ( GetByte() == 3 ) + { + // First, get the query criteria range. + ::std::unique_ptr<ScDBRangeBase> pQueryRef( PopDBDoubleRef() ); + if (!pQueryRef) + return nullptr; + + bool bByVal = true; + double nVal = 0.0; + svl::SharedString aStr; + ScRange aMissingRange; + bool bRangeFake = false; + switch (GetStackType()) + { + case svDouble : + nVal = ::rtl::math::approxFloor( GetDouble() ); + if ( bAllowMissingField && nVal == 0.0 ) + rMissingField = true; // fake missing parameter + break; + case svString : + bByVal = false; + aStr = GetString(); + break; + case svSingleRef : + { + ScAddress aAdr; + PopSingleRef( aAdr ); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + nVal = GetCellValue(aAdr, aCell); + else + { + bByVal = false; + GetCellString(aStr, aCell); + } + } + break; + case svDoubleRef : + if ( bAllowMissingField ) + { // fake missing parameter for old SO compatibility + bRangeFake = true; + PopDoubleRef( aMissingRange ); + } + else + { + PopError(); + SetError( FormulaError::IllegalParameter ); + } + break; + case svMissing : + PopError(); + if ( bAllowMissingField ) + rMissingField = true; + else + SetError( FormulaError::IllegalParameter ); + break; + default: + PopError(); + SetError( FormulaError::IllegalParameter ); + } + + if (nGlobalError != FormulaError::NONE) + return nullptr; + + unique_ptr<ScDBRangeBase> pDBRef( PopDBDoubleRef() ); + + if (nGlobalError != FormulaError::NONE || !pDBRef) + return nullptr; + + if ( bRangeFake ) + { + // range parameter must match entire database range + if (pDBRef->isRangeEqual(aMissingRange)) + rMissingField = true; + else + SetError( FormulaError::IllegalParameter ); + } + + if (nGlobalError != FormulaError::NONE) + return nullptr; + + SCCOL nField = pDBRef->getFirstFieldColumn(); + if (rMissingField) + ; // special case + else if (bByVal) + nField = pDBRef->findFieldColumn(static_cast<SCCOL>(nVal)); + else + { + FormulaError nErr = FormulaError::NONE; + nField = pDBRef->findFieldColumn(aStr.getString(), &nErr); + SetError(nErr); + } + + if (!mrDoc.ValidCol(nField)) + return nullptr; + + unique_ptr<ScDBQueryParamBase> pParam( pDBRef->createQueryParam(pQueryRef.get()) ); + + if (pParam) + { + // An allowed missing field parameter sets the result field + // to any of the query fields, just to be able to return + // some cell from the iterator. + if ( rMissingField ) + nField = static_cast<SCCOL>(pParam->GetEntry(0).nField); + pParam->mnField = nField; + + SCSIZE nCount = pParam->GetEntryCount(); + for ( SCSIZE i=0; i < nCount; i++ ) + { + ScQueryEntry& rEntry = pParam->GetEntry(i); + if (!rEntry.bDoQuery) + break; + + ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + sal_uInt32 nIndex = 0; + OUString aQueryStr = rItem.maString.getString(); + bool bNumber = pFormatter->IsNumberFormat( + aQueryStr, nIndex, rItem.mfVal); + rItem.meType = bNumber ? ScQueryEntry::ByValue : ScQueryEntry::ByString; + + if (!bNumber && pParam->eSearchType == utl::SearchParam::SearchType::Normal) + pParam->eSearchType = DetectSearchType(aQueryStr, mrDoc); + } + return pParam; + } + } + return nullptr; +} + +void ScInterpreter::DBIterator( ScIterFunc eFunc ) +{ + double fRes = 0; + KahanSum fErg = 0; + sal_uLong nCount = 0; + bool bMissingField = false; + unique_ptr<ScDBQueryParamBase> pQueryParam( GetDBParams(bMissingField) ); + if (pQueryParam) + { + if (!pQueryParam->IsValidFieldIndex()) + { + SetError(FormulaError::NoValue); + return; + } + ScDBQueryDataIterator aValIter(mrDoc, mrContext, std::move(pQueryParam)); + ScDBQueryDataIterator::Value aValue; + if ( aValIter.GetFirst(aValue) && aValue.mnError == FormulaError::NONE ) + { + switch( eFunc ) + { + case ifPRODUCT: fRes = 1; break; + case ifMAX: fRes = -MAXDOUBLE; break; + case ifMIN: fRes = MAXDOUBLE; break; + default: ; // nothing + } + + do + { + nCount++; + switch( eFunc ) + { + case ifAVERAGE: + case ifSUM: + fErg += aValue.mfValue; + break; + case ifSUMSQ: + fErg += aValue.mfValue * aValue.mfValue; + break; + case ifPRODUCT: + fRes *= aValue.mfValue; + break; + case ifMAX: + if( aValue.mfValue > fRes ) fRes = aValue.mfValue; + break; + case ifMIN: + if( aValue.mfValue < fRes ) fRes = aValue.mfValue; + break; + default: ; // nothing + } + } + while ( aValIter.GetNext(aValue) && aValue.mnError == FormulaError::NONE ); + } + SetError(aValue.mnError); + } + else + SetError( FormulaError::IllegalParameter); + switch( eFunc ) + { + case ifCOUNT: fRes = nCount; break; + case ifSUM: fRes = fErg.get(); break; + case ifSUMSQ: fRes = fErg.get(); break; + case ifAVERAGE: fRes = div(fErg.get(), nCount); break; + default: ; // nothing + } + PushDouble( fRes ); +} + +void ScInterpreter::ScDBSum() +{ + DBIterator( ifSUM ); +} + +void ScInterpreter::ScDBCount() +{ + bool bMissingField = true; + unique_ptr<ScDBQueryParamBase> pQueryParam( GetDBParams(bMissingField) ); + if (pQueryParam) + { + sal_uLong nCount = 0; + if ( bMissingField && pQueryParam->GetType() == ScDBQueryParamBase::INTERNAL ) + { // count all matching records + // TODO: currently the QueryIterators only return cell pointers of + // existing cells, so if a query matches an empty cell there's + // nothing returned, and therefore not counted! + // Since this has ever been the case and this code here only came + // into existence to fix #i6899 and it never worked before we'll + // have to live with it until we reimplement the iterators to also + // return empty cells, which would mean to adapt all callers of + // iterators. + ScDBQueryParamInternal* p = static_cast<ScDBQueryParamInternal*>(pQueryParam.get()); + p->nCol2 = p->nCol1; // Don't forget to select only one column. + SCTAB nTab = p->nTab; + // ScQueryCellIteratorDirect doesn't make use of ScDBQueryParamBase::mnField, + // so the source range has to be restricted, like before the introduction + // of ScDBQueryParamBase. + p->nCol1 = p->nCol2 = p->mnField; + ScQueryCellIteratorDirect aCellIter(mrDoc, mrContext, nTab, *p, true); + if ( aCellIter.GetFirst() ) + { + do + { + nCount++; + } while ( aCellIter.GetNext() ); + } + } + else + { // count only matching records with a value in the "result" field + if (!pQueryParam->IsValidFieldIndex()) + { + SetError(FormulaError::NoValue); + return; + } + ScDBQueryDataIterator aValIter(mrDoc, mrContext, std::move(pQueryParam)); + ScDBQueryDataIterator::Value aValue; + if ( aValIter.GetFirst(aValue) && aValue.mnError == FormulaError::NONE ) + { + do + { + nCount++; + } + while ( aValIter.GetNext(aValue) && aValue.mnError == FormulaError::NONE ); + } + SetError(aValue.mnError); + } + PushDouble( nCount ); + } + else + PushIllegalParameter(); +} + +void ScInterpreter::ScDBCount2() +{ + bool bMissingField = true; + unique_ptr<ScDBQueryParamBase> pQueryParam( GetDBParams(bMissingField) ); + if (pQueryParam) + { + if (!pQueryParam->IsValidFieldIndex()) + { + SetError(FormulaError::NoValue); + return; + } + sal_uLong nCount = 0; + pQueryParam->mbSkipString = false; + ScDBQueryDataIterator aValIter(mrDoc, mrContext, std::move(pQueryParam)); + ScDBQueryDataIterator::Value aValue; + if ( aValIter.GetFirst(aValue) && aValue.mnError == FormulaError::NONE ) + { + do + { + nCount++; + } + while ( aValIter.GetNext(aValue) && aValue.mnError == FormulaError::NONE ); + } + SetError(aValue.mnError); + PushDouble( nCount ); + } + else + PushIllegalParameter(); +} + +void ScInterpreter::ScDBAverage() +{ + DBIterator( ifAVERAGE ); +} + +void ScInterpreter::ScDBMax() +{ + DBIterator( ifMAX ); +} + +void ScInterpreter::ScDBMin() +{ + DBIterator( ifMIN ); +} + +void ScInterpreter::ScDBProduct() +{ + DBIterator( ifPRODUCT ); +} + +void ScInterpreter::GetDBStVarParams( double& rVal, double& rValCount ) +{ + std::vector<double> values; + KahanSum vSum = 0.0; + KahanSum fSum = 0.0; + + rValCount = 0.0; + bool bMissingField = false; + unique_ptr<ScDBQueryParamBase> pQueryParam( GetDBParams(bMissingField) ); + if (pQueryParam) + { + if (!pQueryParam->IsValidFieldIndex()) + { + SetError(FormulaError::NoValue); + return; + } + ScDBQueryDataIterator aValIter(mrDoc, mrContext, std::move(pQueryParam)); + ScDBQueryDataIterator::Value aValue; + if (aValIter.GetFirst(aValue) && aValue.mnError == FormulaError::NONE) + { + do + { + rValCount++; + values.push_back(aValue.mfValue); + fSum += aValue.mfValue; + } + while ((aValue.mnError == FormulaError::NONE) && aValIter.GetNext(aValue)); + } + SetError(aValue.mnError); + } + else + SetError( FormulaError::IllegalParameter); + + double vMean = fSum.get() / values.size(); + + for (double v : values) + vSum += (v - vMean) * (v - vMean); + + rVal = vSum.get(); +} + +void ScInterpreter::ScDBStdDev() +{ + double fVal, fCount; + GetDBStVarParams( fVal, fCount ); + PushDouble( sqrt(fVal/(fCount-1))); +} + +void ScInterpreter::ScDBStdDevP() +{ + double fVal, fCount; + GetDBStVarParams( fVal, fCount ); + PushDouble( sqrt(fVal/fCount)); +} + +void ScInterpreter::ScDBVar() +{ + double fVal, fCount; + GetDBStVarParams( fVal, fCount ); + PushDouble(fVal/(fCount-1)); +} + +void ScInterpreter::ScDBVarP() +{ + double fVal, fCount; + GetDBStVarParams( fVal, fCount ); + PushDouble(fVal/fCount); +} + +static bool lcl_IsTableStructuredRef(const OUString& sRefStr, sal_Int32& nIndex) +{ + nIndex = ScGlobal::FindUnquoted(sRefStr, '['); + return (nIndex > 0 && ScGlobal::FindUnquoted(sRefStr, ']', nIndex + 1) > nIndex); +} + +void ScInterpreter::ScIndirect() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 2 ) ) + return; + + // Reference address syntax for INDIRECT is configurable. + FormulaGrammar::AddressConvention eConv = maCalcConfig.meStringRefAddressSyntax; + if (eConv == FormulaGrammar::CONV_UNSPECIFIED) + // Use the current address syntax if unspecified. + eConv = mrDoc.GetAddressConvention(); + + // either CONV_A1_XL_A1 was explicitly configured, or it wasn't possible + // to determine which syntax to use during doc import + bool bTryXlA1 = (eConv == FormulaGrammar::CONV_A1_XL_A1); + + if (nParamCount == 2 && 0.0 == GetDouble() ) + { + // Overwrite the config and try Excel R1C1. + eConv = FormulaGrammar::CONV_XL_R1C1; + bTryXlA1 = false; + } + + svl::SharedString sSharedRefStr = GetString(); + const OUString & sRefStr = sSharedRefStr.getString(); + if (sRefStr.isEmpty()) + { + // Bail out early for empty cells, rely on "we do have a string" below. + PushError( FormulaError::NoRef); + return; + } + + const ScAddress::Details aDetails( bTryXlA1 ? FormulaGrammar::CONV_OOO : eConv, aPos ); + const ScAddress::Details aDetailsXlA1( FormulaGrammar::CONV_XL_A1, aPos ); + SCTAB nTab = aPos.Tab(); + + bool bTableRefNamed = false; + sal_Int32 nTableRefNamedIndex = -1; + OUString sTabRefStr; + + // Named expressions and DB range names need to be tried first, as older 1K + // columns allowed names that would now match a 16k columns cell address. + do + { + ScRangeData* pData = ScRangeStringConverter::GetRangeDataFromString( sRefStr, nTab, mrDoc, eConv); + if (!pData) + break; + + // We need this in order to obtain a good range. + pData->ValidateTabRefs(); + + ScRange aRange; + + // This is the usual way to treat named ranges containing + // relative references. + if (!pData->IsReference(aRange, aPos)) + { + sTabRefStr = pData->GetSymbol(); + bTableRefNamed = lcl_IsTableStructuredRef(sTabRefStr, nTableRefNamedIndex); + // if bTableRefNamed is true, we have a name that maps to a table structured reference. + // Such a case is handled below. + break; + } + + if (aRange.aStart == aRange.aEnd) + PushSingleRef( aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aStart.Tab()); + else + PushDoubleRef( aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aStart.Tab(), aRange.aEnd.Col(), + aRange.aEnd.Row(), aRange.aEnd.Tab()); + + // success! + return; + } + while (false); + + do + { + if (bTableRefNamed) + break; + + const OUString & aName( sSharedRefStr.getIgnoreCaseString() ); + ScDBCollection::NamedDBs& rDBs = mrDoc.GetDBCollection()->getNamedDBs(); + const ScDBData* pData = rDBs.findByUpperName( aName); + if (!pData) + break; + + ScRange aRange; + pData->GetArea( aRange); + + // In Excel, specifying a table name without [] resolves to the + // same as with [], a range that excludes header and totals + // rows and contains only data rows. Do the same. + if (pData->HasHeader()) + aRange.aStart.IncRow(); + if (pData->HasTotals()) + aRange.aEnd.IncRow(-1); + + if (aRange.aStart.Row() > aRange.aEnd.Row()) + break; + + if (aRange.aStart == aRange.aEnd) + PushSingleRef( aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aStart.Tab()); + else + PushDoubleRef( aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aStart.Tab(), aRange.aEnd.Col(), + aRange.aEnd.Row(), aRange.aEnd.Tab()); + + // success! + return; + } + while (false); + + ScRefAddress aRefAd, aRefAd2; + ScAddress::ExternalInfo aExtInfo; + if ( !bTableRefNamed && + (ConvertDoubleRef(mrDoc, sRefStr, nTab, aRefAd, aRefAd2, aDetails, &aExtInfo) || + ( bTryXlA1 && ConvertDoubleRef(mrDoc, sRefStr, nTab, aRefAd, + aRefAd2, aDetailsXlA1, &aExtInfo) ) ) ) + { + if (aExtInfo.mbExternal) + { + PushExternalDoubleRef( + aExtInfo.mnFileId, aExtInfo.maTabName, + aRefAd.Col(), aRefAd.Row(), aRefAd.Tab(), + aRefAd2.Col(), aRefAd2.Row(), aRefAd2.Tab()); + } + else + PushDoubleRef( aRefAd, aRefAd2); + } + else if ( !bTableRefNamed && + (ConvertSingleRef(mrDoc, sRefStr, nTab, aRefAd, aDetails, &aExtInfo) || + ( bTryXlA1 && ConvertSingleRef (mrDoc, sRefStr, nTab, aRefAd, + aDetailsXlA1, &aExtInfo) ) ) ) + { + if (aExtInfo.mbExternal) + { + PushExternalSingleRef( + aExtInfo.mnFileId, aExtInfo.maTabName, aRefAd.Col(), aRefAd.Row(), aRefAd.Tab()); + } + else + PushSingleRef( aRefAd); + } + else + { + // It may be even a TableRef or an external name. + // Anything else that resolves to one reference could be added + // here, but we don't want to compile every arbitrary string. This + // is already nasty enough... + sal_Int32 nIndex = bTableRefNamed ? nTableRefNamedIndex : -1; + bool bTableRef = bTableRefNamed; + if (!bTableRefNamed) + bTableRef = lcl_IsTableStructuredRef(sRefStr, nIndex); + bool bExternalName = false; // External references would had been consumed above already. + if (!bTableRef) + { + // This is our own file name reference representation centric.. but + // would work also for XL '[doc]'!name and also for + // '[doc]Sheet1'!name ... sickos. + if (sRefStr[0] == '\'') + { + // Minimum 'a'#name or 'a'!name + // bTryXlA1 means try both, first our own. + if (bTryXlA1 || eConv == FormulaGrammar::CONV_OOO) + { + nIndex = ScGlobal::FindUnquoted( sRefStr, '#'); + if (nIndex >= 3 && sRefStr[nIndex-1] == '\'') + { + bExternalName = true; + eConv = FormulaGrammar::CONV_OOO; + } + } + if (!bExternalName && (bTryXlA1 || eConv != FormulaGrammar::CONV_OOO)) + { + nIndex = ScGlobal::FindUnquoted( sRefStr, '!'); + if (nIndex >= 3 && sRefStr[nIndex-1] == '\'') + { + bExternalName = true; + } + } + } + + } + if (bExternalName || bTableRef) + { + do + { + ScCompiler aComp( mrDoc, aPos, mrDoc.GetGrammar()); + aComp.SetRefConvention( eConv); // must be after grammar + std::unique_ptr<ScTokenArray> pTokArr( aComp.CompileString(bTableRefNamed ? sTabRefStr : sRefStr)); + + if (pTokArr->GetCodeError() != FormulaError::NONE || !pTokArr->GetLen()) + break; + + // Whatever... use only the specific case. + if (bExternalName) + { + const formula::FormulaToken* pTok = pTokArr->FirstToken(); + if (!pTok || pTok->GetType() != svExternalName) + break; + } + else if (!pTokArr->HasOpCode( ocTableRef)) + break; + + aComp.CompileTokenArray(); + + // A syntactically valid reference will generate exactly + // one RPN token, a reference or error. Discard everything + // else as error. + if (pTokArr->GetCodeLen() != 1) + break; + + ScTokenRef xTok( pTokArr->FirstRPNToken()); + if (!xTok) + break; + + switch (xTok->GetType()) + { + case svSingleRef: + case svDoubleRef: + case svExternalSingleRef: + case svExternalDoubleRef: + case svError: + PushTokenRef( xTok); + // success! + return; + default: + ; // nothing + } + } + while (false); + } + + PushError( FormulaError::NoRef); + } +} + +void ScInterpreter::ScAddressFunc() +{ + OUString sTabStr; + + sal_uInt8 nParamCount = GetByte(); + if( !MustHaveParamCount( nParamCount, 2, 5 ) ) + return; + + if( nParamCount >= 5 ) + sTabStr = GetString().getString(); + + FormulaGrammar::AddressConvention eConv = FormulaGrammar::CONV_OOO; // default + if (nParamCount >= 4 && 0.0 == GetDoubleWithDefault( 1.0)) + eConv = FormulaGrammar::CONV_XL_R1C1; + else + { + // If A1 syntax is requested then the actual sheet separator and format + // convention depends on the syntax configured for INDIRECT to match + // that, and if it is unspecified then the document's address syntax. + FormulaGrammar::AddressConvention eForceConv = maCalcConfig.meStringRefAddressSyntax; + if (eForceConv == FormulaGrammar::CONV_UNSPECIFIED) + eForceConv = mrDoc.GetAddressConvention(); + if (eForceConv == FormulaGrammar::CONV_XL_A1 || eForceConv == FormulaGrammar::CONV_XL_R1C1) + eConv = FormulaGrammar::CONV_XL_A1; // for anything Excel use Excel A1 + } + + ScRefFlags nFlags = ScRefFlags::COL_ABS | ScRefFlags::ROW_ABS; // default + if( nParamCount >= 3 ) + { + sal_Int32 n = GetInt32WithDefault(1); + switch ( n ) + { + default : + PushNoValue(); + return; + + case 5: + case 1 : break; // default + case 6: + case 2 : nFlags = ScRefFlags::ROW_ABS; break; + case 7: + case 3 : nFlags = ScRefFlags::COL_ABS; break; + case 8: + case 4 : nFlags = ScRefFlags::ZERO; break; // both relative + } + } + nFlags |= ScRefFlags::VALID | ScRefFlags::ROW_VALID | ScRefFlags::COL_VALID; + + SCCOL nCol = static_cast<SCCOL>(GetInt16()); + SCROW nRow = static_cast<SCROW>(GetInt32()); + if( eConv == FormulaGrammar::CONV_XL_R1C1 ) + { + // YUCK! The XL interface actually treats rel R1C1 refs differently + // than A1 + if( !(nFlags & ScRefFlags::COL_ABS) ) + nCol += aPos.Col() + 1; + if( !(nFlags & ScRefFlags::ROW_ABS) ) + nRow += aPos.Row() + 1; + } + + --nCol; + --nRow; + if (nGlobalError != FormulaError::NONE || !mrDoc.ValidCol( nCol) || !mrDoc.ValidRow( nRow)) + { + PushIllegalArgument(); + return; + } + + const ScAddress::Details aDetails( eConv, aPos ); + const ScAddress aAdr( nCol, nRow, 0); + OUString aRefStr(aAdr.Format(nFlags, &mrDoc, aDetails)); + + if( nParamCount >= 5 && !sTabStr.isEmpty() ) + { + OUString aDoc; + if (eConv == FormulaGrammar::CONV_OOO) + { + // Isolate Tab from 'Doc'#Tab + sal_Int32 nPos = ScCompiler::GetDocTabPos( sTabStr); + if (nPos != -1) + { + if (sTabStr[nPos+1] == '$') + ++nPos; // also split 'Doc'#$Tab + aDoc = sTabStr.copy( 0, nPos+1); + sTabStr = sTabStr.copy( nPos+1); + } + } + /* TODO: yet unsupported external reference in CONV_XL_R1C1 syntax may + * need some extra handling to isolate Tab from Doc. */ + if (sTabStr[0] != '\'' || !sTabStr.endsWith("'")) + ScCompiler::CheckTabQuotes( sTabStr, eConv); + if (!aDoc.isEmpty()) + sTabStr = aDoc + sTabStr; + sTabStr += (eConv == FormulaGrammar::CONV_XL_R1C1 || eConv == FormulaGrammar::CONV_XL_A1) ? + std::u16string_view(u"!") : std::u16string_view(u"."); + sTabStr += aRefStr; + PushString( sTabStr ); + } + else + PushString( aRefStr ); +} + +void ScInterpreter::ScOffset() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 3, 5 ) ) + return; + + bool bNewWidth = false; + bool bNewHeight = false; + sal_Int32 nColNew = 1, nRowNew = 1; + if (nParamCount == 5) + { + if (IsMissing()) + PopError(); + else + { + nColNew = GetInt32(); + bNewWidth = true; + } + } + if (nParamCount >= 4) + { + if (IsMissing()) + PopError(); + else + { + nRowNew = GetInt32(); + bNewHeight = true; + } + } + sal_Int32 nColPlus = GetInt32(); + sal_Int32 nRowPlus = GetInt32(); + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + if (nColNew <= 0 || nRowNew <= 0) + { + PushIllegalArgument(); + return; + } + SCCOL nCol1(0); + SCROW nRow1(0); + SCTAB nTab1(0); + SCCOL nCol2(0); + SCROW nRow2(0); + SCTAB nTab2(0); + switch (GetStackType()) + { + case svSingleRef: + { + PopSingleRef(nCol1, nRow1, nTab1); + if (!bNewWidth && !bNewHeight) + { + nCol1 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1) + nColPlus); + nRow1 = static_cast<SCROW>(static_cast<tools::Long>(nRow1) + nRowPlus); + if (!mrDoc.ValidCol(nCol1) || !mrDoc.ValidRow(nRow1)) + PushIllegalArgument(); + else + PushSingleRef(nCol1, nRow1, nTab1); + } + else + { + nCol1 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1)+nColPlus); + nRow1 = static_cast<SCROW>(static_cast<tools::Long>(nRow1)+nRowPlus); + nCol2 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1)+nColNew-1); + nRow2 = static_cast<SCROW>(static_cast<tools::Long>(nRow1)+nRowNew-1); + if (!mrDoc.ValidCol(nCol1) || !mrDoc.ValidRow(nRow1) || + !mrDoc.ValidCol(nCol2) || !mrDoc.ValidRow(nRow2)) + PushIllegalArgument(); + else + PushDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab1); + } + break; + } + case svExternalSingleRef: + { + sal_uInt16 nFileId; + OUString aTabName; + ScSingleRefData aRef; + PopExternalSingleRef(nFileId, aTabName, aRef); + ScAddress aAbsRef = aRef.toAbs(mrDoc, aPos); + nCol1 = aAbsRef.Col(); + nRow1 = aAbsRef.Row(); + nTab1 = aAbsRef.Tab(); + + if (!bNewWidth && !bNewHeight) + { + nCol1 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1) + nColPlus); + nRow1 = static_cast<SCROW>(static_cast<tools::Long>(nRow1) + nRowPlus); + if (!mrDoc.ValidCol(nCol1) || !mrDoc.ValidRow(nRow1)) + PushIllegalArgument(); + else + PushExternalSingleRef(nFileId, aTabName, nCol1, nRow1, nTab1); + } + else + { + nCol1 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1)+nColPlus); + nRow1 = static_cast<SCROW>(static_cast<tools::Long>(nRow1)+nRowPlus); + nCol2 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1)+nColNew-1); + nRow2 = static_cast<SCROW>(static_cast<tools::Long>(nRow1)+nRowNew-1); + nTab2 = nTab1; + if (!mrDoc.ValidCol(nCol1) || !mrDoc.ValidRow(nRow1) || + !mrDoc.ValidCol(nCol2) || !mrDoc.ValidRow(nRow2)) + PushIllegalArgument(); + else + PushExternalDoubleRef(nFileId, aTabName, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + } + break; + } + case svDoubleRef: + { + PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + if (!bNewWidth) + nColNew = nCol2 - nCol1 + 1; + if (!bNewHeight) + nRowNew = nRow2 - nRow1 + 1; + nCol1 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1)+nColPlus); + nRow1 = static_cast<SCROW>(static_cast<tools::Long>(nRow1)+nRowPlus); + nCol2 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1)+nColNew-1); + nRow2 = static_cast<SCROW>(static_cast<tools::Long>(nRow1)+nRowNew-1); + if (!mrDoc.ValidCol(nCol1) || !mrDoc.ValidRow(nRow1) || + !mrDoc.ValidCol(nCol2) || !mrDoc.ValidRow(nRow2) || nTab1 != nTab2) + PushIllegalArgument(); + else + PushDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab1); + break; + } + case svExternalDoubleRef: + { + sal_uInt16 nFileId; + OUString aTabName; + ScComplexRefData aRef; + PopExternalDoubleRef(nFileId, aTabName, aRef); + ScRange aAbs = aRef.toAbs(mrDoc, aPos); + nCol1 = aAbs.aStart.Col(); + nRow1 = aAbs.aStart.Row(); + nTab1 = aAbs.aStart.Tab(); + nCol2 = aAbs.aEnd.Col(); + nRow2 = aAbs.aEnd.Row(); + nTab2 = aAbs.aEnd.Tab(); + if (!bNewWidth) + nColNew = nCol2 - nCol1 + 1; + if (!bNewHeight) + nRowNew = nRow2 - nRow1 + 1; + nCol1 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1)+nColPlus); + nRow1 = static_cast<SCROW>(static_cast<tools::Long>(nRow1)+nRowPlus); + nCol2 = static_cast<SCCOL>(static_cast<tools::Long>(nCol1)+nColNew-1); + nRow2 = static_cast<SCROW>(static_cast<tools::Long>(nRow1)+nRowNew-1); + if (!mrDoc.ValidCol(nCol1) || !mrDoc.ValidRow(nRow1) || + !mrDoc.ValidCol(nCol2) || !mrDoc.ValidRow(nRow2) || nTab1 != nTab2) + PushIllegalArgument(); + else + PushExternalDoubleRef(nFileId, aTabName, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + break; + } + default: + PushIllegalParameter(); + break; + } // end switch +} + +void ScInterpreter::ScIndex() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 4 ) ) + return; + + sal_uInt32 nArea; + size_t nAreaCount; + SCCOL nCol; + SCROW nRow; + if (nParamCount == 4) + nArea = GetUInt32(); + else + nArea = 1; + bool bColMissing; + if (nParamCount >= 3) + { + bColMissing = IsMissing(); + nCol = static_cast<SCCOL>(GetInt16()); + } + else + { + bColMissing = false; + nCol = 0; + } + if (nParamCount >= 2) + nRow = static_cast<SCROW>(GetInt32()); + else + nRow = 0; + if (GetStackType() == svRefList) + nAreaCount = (sp ? pStack[sp-1]->GetRefList()->size() : 0); + else + nAreaCount = 1; // one reference or array or whatever + if (nGlobalError != FormulaError::NONE || nAreaCount == 0 || static_cast<size_t>(nArea) > nAreaCount) + { + PushError( FormulaError::NoRef); + return; + } + else if (nArea < 1 || nCol < 0 || nRow < 0) + { + PushIllegalArgument(); + return; + } + switch (GetStackType()) + { + case svMatrix: + case svExternalSingleRef: + case svExternalDoubleRef: + { + if (nArea != 1) + SetError(FormulaError::IllegalArgument); + sal_uInt16 nOldSp = sp; + ScMatrixRef pMat = GetMatrix(); + if (pMat) + { + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + + // Access one element of a vector independent of col/row + // orientation. Excel documentation does not mention, but + // i62850 had a .xls example of a row vector accessed by + // row number returning one element. This + // INDEX(row_vector;element) behaves the same as + // INDEX(row_vector;0;element) and thus contradicts Excel + // documentation where the second parameter is always + // row_num. + // + // ODFF v1.3 in 6.14.6 INDEX states "If DataSource is a + // one-dimensional row vector, Row is optional, which + // effectively makes Row act as the column offset into the + // vector". Guess the first Row is a typo and should read + // Column instead. + + const bool bRowVectorSpecial = (nParamCount == 2 || bColMissing); + const bool bRowVectorElement = (nR == 1 && (nCol != 0 || (bRowVectorSpecial && nRow != 0))); + const bool bVectorElement = (bRowVectorElement || (nC == 1 && nRow != 0)); + + if (nC == 0 || nR == 0 || + (!bVectorElement && (o3tl::make_unsigned(nCol) > nC || + o3tl::make_unsigned(nRow) > nR))) + PushError( FormulaError::NoRef); + else if (nCol == 0 && nRow == 0) + sp = nOldSp; + else if (bVectorElement) + { + // Vectors here don't replicate to the other dimension. + SCSIZE nElement, nOtherDimension; + if (bRowVectorElement && !bRowVectorSpecial) + { + nElement = o3tl::make_unsigned(nCol); + nOtherDimension = o3tl::make_unsigned(nRow); + } + else + { + nElement = o3tl::make_unsigned(nRow); + nOtherDimension = o3tl::make_unsigned(nCol); + } + + if (nElement == 0 || nElement > nC * nR || nOtherDimension > 1) + PushError( FormulaError::NoRef); + else + { + --nElement; + if (pMat->IsStringOrEmpty( nElement)) + PushString( pMat->GetString(nElement).getString()); + else + PushDouble( pMat->GetDouble( nElement)); + } + } + else if (nCol == 0) + { + ScMatrixRef pResMat = GetNewMat(nC, 1, /*bEmpty*/true); + if (pResMat) + { + SCSIZE nRowMinus1 = static_cast<SCSIZE>(nRow - 1); + for (SCSIZE i = 0; i < nC; i++) + if (!pMat->IsStringOrEmpty(i, nRowMinus1)) + pResMat->PutDouble(pMat->GetDouble(i, + nRowMinus1), i, 0); + else + pResMat->PutString(pMat->GetString(i, nRowMinus1), i, 0); + + PushMatrix(pResMat); + } + else + PushError( FormulaError::NoRef); + } + else if (nRow == 0) + { + ScMatrixRef pResMat = GetNewMat(1, nR, /*bEmpty*/true); + if (pResMat) + { + SCSIZE nColMinus1 = static_cast<SCSIZE>(nCol - 1); + for (SCSIZE i = 0; i < nR; i++) + if (!pMat->IsStringOrEmpty(nColMinus1, i)) + pResMat->PutDouble(pMat->GetDouble(nColMinus1, + i), i); + else + pResMat->PutString(pMat->GetString(nColMinus1, i), i); + PushMatrix(pResMat); + } + else + PushError( FormulaError::NoRef); + } + else + { + if (!pMat->IsStringOrEmpty( static_cast<SCSIZE>(nCol-1), + static_cast<SCSIZE>(nRow-1))) + PushDouble( pMat->GetDouble( + static_cast<SCSIZE>(nCol-1), + static_cast<SCSIZE>(nRow-1))); + else + PushString( pMat->GetString( + static_cast<SCSIZE>(nCol-1), + static_cast<SCSIZE>(nRow-1)).getString()); + } + } + } + break; + case svSingleRef: + { + SCCOL nCol1 = 0; + SCROW nRow1 = 0; + SCTAB nTab1 = 0; + PopSingleRef( nCol1, nRow1, nTab1); + if (nCol > 1 || nRow > 1) + PushError( FormulaError::NoRef); + else + PushSingleRef( nCol1, nRow1, nTab1); + } + break; + case svDoubleRef: + case svRefList: + { + SCCOL nCol1 = 0; + SCROW nRow1 = 0; + SCTAB nTab1 = 0; + SCCOL nCol2 = 0; + SCROW nRow2 = 0; + SCTAB nTab2 = 0; + bool bRowArray = false; + if (GetStackType() == svRefList) + { + FormulaConstTokenRef xRef = PopToken(); + if (nGlobalError != FormulaError::NONE || !xRef) + { + PushError( FormulaError::NoRef); + return; + } + ScRange aRange( ScAddress::UNINITIALIZED); + DoubleRefToRange( (*(xRef->GetRefList()))[nArea-1], aRange); + aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + if ( nParamCount == 2 && nRow1 == nRow2 ) + bRowArray = true; + } + else + { + PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + if ( nParamCount == 2 && nRow1 == nRow2 ) + bRowArray = true; + } + if ( nTab1 != nTab2 || + (nCol > 0 && nCol1+nCol-1 > nCol2) || + (nRow > 0 && nRow1+nRow-1 > nRow2 && !bRowArray ) || + ( nRow > nCol2 - nCol1 + 1 && bRowArray )) + PushError( FormulaError::NoRef); + else if (nCol == 0 && nRow == 0) + { + if ( nCol1 == nCol2 && nRow1 == nRow2 ) + PushSingleRef( nCol1, nRow1, nTab1 ); + else + PushDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab1 ); + } + else if (nRow == 0) + { + if ( nRow1 == nRow2 ) + PushSingleRef( nCol1+nCol-1, nRow1, nTab1 ); + else + PushDoubleRef( nCol1+nCol-1, nRow1, nTab1, + nCol1+nCol-1, nRow2, nTab1 ); + } + else if (nCol == 0) + { + if ( nCol1 == nCol2 ) + PushSingleRef( nCol1, nRow1+nRow-1, nTab1 ); + else if ( bRowArray ) + { + nCol =static_cast<SCCOL>(nRow); + nRow = 1; + PushSingleRef( nCol1+nCol-1, nRow1+nRow-1, nTab1); + } + else + PushDoubleRef( nCol1, nRow1+nRow-1, nTab1, + nCol2, nRow1+nRow-1, nTab1); + } + else + PushSingleRef( nCol1+nCol-1, nRow1+nRow-1, nTab1); + } + break; + default: + PopError(); + PushError( FormulaError::NoRef); + } +} + +void ScInterpreter::ScMultiArea() +{ + // Legacy support, convert to RefList + sal_uInt8 nParamCount = GetByte(); + if (MustHaveParamCountMin( nParamCount, 1)) + { + while (nGlobalError == FormulaError::NONE && nParamCount-- > 1) + { + ScUnionFunc(); + } + } +} + +void ScInterpreter::ScAreas() +{ + sal_uInt8 nParamCount = GetByte(); + if (!MustHaveParamCount( nParamCount, 1)) + return; + + size_t nCount = 0; + switch (GetStackType()) + { + case svSingleRef: + { + FormulaConstTokenRef xT = PopToken(); + ValidateRef( *xT->GetSingleRef()); + ++nCount; + } + break; + case svDoubleRef: + { + FormulaConstTokenRef xT = PopToken(); + ValidateRef( *xT->GetDoubleRef()); + ++nCount; + } + break; + case svRefList: + { + FormulaConstTokenRef xT = PopToken(); + ValidateRef( *(xT->GetRefList())); + nCount += xT->GetRefList()->size(); + } + break; + default: + SetError( FormulaError::IllegalParameter); + } + PushDouble( double(nCount)); +} + +void ScInterpreter::ScCurrency() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 2 ) ) + return; + + OUString aStr; + double fDec; + if (nParamCount == 2) + { + fDec = ::rtl::math::approxFloor(GetDouble()); + if (fDec < -15.0 || fDec > 15.0) + { + PushIllegalArgument(); + return; + } + } + else + fDec = 2.0; + double fVal = GetDouble(); + double fFac; + if ( fDec != 0.0 ) + fFac = pow( double(10), fDec ); + else + fFac = 1.0; + if (fVal < 0.0) + fVal = ceil(fVal*fFac-0.5)/fFac; + else + fVal = floor(fVal*fFac+0.5)/fFac; + const Color* pColor = nullptr; + if ( fDec < 0.0 ) + fDec = 0.0; + sal_uLong nIndex = pFormatter->GetStandardFormat( + SvNumFormatType::CURRENCY, + ScGlobal::eLnge); + if ( static_cast<sal_uInt16>(fDec) != pFormatter->GetFormatPrecision( nIndex ) ) + { + OUString sFormatString = pFormatter->GenerateFormat( + nIndex, + ScGlobal::eLnge, + true, // with thousands separator + false, // not red + static_cast<sal_uInt16>(fDec));// decimal places + if (!pFormatter->GetPreviewString(sFormatString, + fVal, + aStr, + &pColor, + ScGlobal::eLnge)) + SetError(FormulaError::IllegalArgument); + } + else + { + pFormatter->GetOutputString(fVal, nIndex, aStr, &pColor); + } + PushString(aStr); +} + +void ScInterpreter::ScReplace() +{ + if ( !MustHaveParamCount( GetByte(), 4 ) ) + return; + + OUString aNewStr = GetString().getString(); + sal_Int32 nCount = GetStringPositionArgument(); + sal_Int32 nPos = GetStringPositionArgument(); + OUString aOldStr = GetString().getString(); + if (nPos < 1 || nCount < 0) + PushIllegalArgument(); + else + { + sal_Int32 nLen = aOldStr.getLength(); + if (nPos > nLen + 1) + nPos = nLen + 1; + if (nCount > nLen - nPos + 1) + nCount = nLen - nPos + 1; + sal_Int32 nIdx = 0; + sal_Int32 nCnt = 0; + while ( nIdx < nLen && nPos > nCnt + 1 ) + { + aOldStr.iterateCodePoints( &nIdx ); + ++nCnt; + } + sal_Int32 nStart = nIdx; + while ( nIdx < nLen && nPos + nCount - 1 > nCnt ) + { + aOldStr.iterateCodePoints( &nIdx ); + ++nCnt; + } + if ( CheckStringResultLen( aOldStr, aNewStr.getLength() - (nIdx - nStart) ) ) + aOldStr = aOldStr.replaceAt( nStart, nIdx - nStart, aNewStr ); + PushString( aOldStr ); + } +} + +void ScInterpreter::ScFixed() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 3 ) ) + return; + + OUString aStr; + double fDec; + bool bThousand; + if (nParamCount == 3) + bThousand = !GetBool(); // Param true: no thousands separator + else + bThousand = true; + if (nParamCount >= 2) + { + fDec = ::rtl::math::approxFloor(GetDoubleWithDefault( 2.0 )); + if (fDec < -15.0 || fDec > 15.0) + { + PushIllegalArgument(); + return; + } + } + else + fDec = 2.0; + double fVal = GetDouble(); + double fFac; + if ( fDec != 0.0 ) + fFac = pow( double(10), fDec ); + else + fFac = 1.0; + if (fVal < 0.0) + fVal = ceil(fVal*fFac-0.5)/fFac; + else + fVal = floor(fVal*fFac+0.5)/fFac; + const Color* pColor = nullptr; + if (fDec < 0.0) + fDec = 0.0; + sal_uLong nIndex = pFormatter->GetStandardFormat( + SvNumFormatType::NUMBER, + ScGlobal::eLnge); + OUString sFormatString = pFormatter->GenerateFormat( + nIndex, + ScGlobal::eLnge, + bThousand, // with thousands separator + false, // not red + static_cast<sal_uInt16>(fDec));// decimal places + if (!pFormatter->GetPreviewString(sFormatString, + fVal, + aStr, + &pColor, + ScGlobal::eLnge)) + PushIllegalArgument(); + else + PushString(aStr); +} + +void ScInterpreter::ScFind() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2, 3 ) ) + return; + + sal_Int32 nCnt; + if (nParamCount == 3) + nCnt = GetDouble(); + else + nCnt = 1; + OUString sStr = GetString().getString(); + if (nCnt < 1 || nCnt > sStr.getLength()) + PushNoValue(); + else + { + sal_Int32 nPos = sStr.indexOf(GetString().getString(), nCnt - 1); + if (nPos == -1) + PushNoValue(); + else + { + sal_Int32 nIdx = 0; + nCnt = 0; + while ( nIdx < nPos ) + { + sStr.iterateCodePoints( &nIdx ); + ++nCnt; + } + PushDouble( static_cast<double>(nCnt + 1) ); + } + } +} + +void ScInterpreter::ScExact() +{ + nFuncFmtType = SvNumFormatType::LOGICAL; + if ( MustHaveParamCount( GetByte(), 2 ) ) + { + svl::SharedString s1 = GetString(); + svl::SharedString s2 = GetString(); + PushInt( int(s1.getData() == s2.getData()) ); + } +} + +void ScInterpreter::ScLeft() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 2 ) ) + return; + + sal_Int32 n; + if (nParamCount == 2) + { + n = GetStringPositionArgument(); + if (n < 0) + { + PushIllegalArgument(); + return ; + } + } + else + n = 1; + OUString aStr = GetString().getString(); + sal_Int32 nIdx = 0; + sal_Int32 nCnt = 0; + while ( nIdx < aStr.getLength() && n > nCnt++ ) + aStr.iterateCodePoints( &nIdx ); + aStr = aStr.copy( 0, nIdx ); + PushString( aStr ); +} + +namespace { + +struct UBlockScript { + UBlockCode from; + UBlockCode to; +}; + +} + +const UBlockScript scriptList[] = { + {UBLOCK_HANGUL_JAMO, UBLOCK_HANGUL_JAMO}, + {UBLOCK_CJK_RADICALS_SUPPLEMENT, UBLOCK_HANGUL_SYLLABLES}, + {UBLOCK_CJK_COMPATIBILITY_IDEOGRAPHS,UBLOCK_CJK_RADICALS_SUPPLEMENT }, + {UBLOCK_IDEOGRAPHIC_DESCRIPTION_CHARACTERS,UBLOCK_CJK_COMPATIBILITY_IDEOGRAPHS}, + {UBLOCK_CJK_COMPATIBILITY_FORMS, UBLOCK_CJK_COMPATIBILITY_FORMS}, + {UBLOCK_HALFWIDTH_AND_FULLWIDTH_FORMS, UBLOCK_HALFWIDTH_AND_FULLWIDTH_FORMS}, + {UBLOCK_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B, UBLOCK_CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT}, + {UBLOCK_CJK_STROKES, UBLOCK_CJK_STROKES} +}; +static bool IsDBCS(sal_Unicode currentChar) +{ + // for the locale of ja-JP, character U+0x005c and U+0x20ac should be ScriptType::Asian + if( (currentChar == 0x005c || currentChar == 0x20ac) && + (MsLangId::getConfiguredSystemLanguage() == LANGUAGE_JAPANESE) ) + return true; + sal_uInt16 i; + bool bRet = false; + UBlockCode block = ublock_getCode(currentChar); + for ( i = 0; i < SAL_N_ELEMENTS(scriptList); i++) { + if (block <= scriptList[i].to) break; + } + bRet = (i < SAL_N_ELEMENTS(scriptList) && block >= scriptList[i].from); + return bRet; +} +static sal_Int32 lcl_getLengthB( std::u16string_view str, sal_Int32 nPos ) +{ + sal_Int32 index = 0; + sal_Int32 length = 0; + while ( index < nPos ) + { + if (IsDBCS(str[index])) + length += 2; + else + length++; + index++; + } + return length; +} +static sal_Int32 getLengthB(std::u16string_view str) +{ + if(str.empty()) + return 0; + else + return lcl_getLengthB( str, str.size() ); +} +void ScInterpreter::ScLenB() +{ + PushDouble( getLengthB(GetString().getString()) ); +} +static OUString lcl_RightB(const OUString &rStr, sal_Int32 n) +{ + if( n < getLengthB(rStr) ) + { + OUStringBuffer aBuf(rStr); + sal_Int32 index = aBuf.getLength(); + while(index-- >= 0) + { + if(0 == n) + { + aBuf.remove( 0, index + 1); + break; + } + if(-1 == n) + { + aBuf.remove( 0, index + 2 ); + aBuf.insert( 0, " "); + break; + } + if(IsDBCS(aBuf[index])) + n -= 2; + else + n--; + } + return aBuf.makeStringAndClear(); + } + return rStr; +} +void ScInterpreter::ScRightB() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 2 ) ) + return; + + sal_Int32 n; + if (nParamCount == 2) + { + n = GetStringPositionArgument(); + if (n < 0) + { + PushIllegalArgument(); + return ; + } + } + else + n = 1; + OUString aStr(lcl_RightB(GetString().getString(), n)); + PushString( aStr ); +} +static OUString lcl_LeftB(const OUString &rStr, sal_Int32 n) +{ + if( n < getLengthB(rStr) ) + { + OUStringBuffer aBuf(rStr); + sal_Int32 index = -1; + while(index++ < aBuf.getLength()) + { + if(0 == n) + { + aBuf.truncate(index); + break; + } + if(-1 == n) + { + aBuf.truncate( index - 1 ); + aBuf.append(" "); + break; + } + if(IsDBCS(aBuf[index])) + n -= 2; + else + n--; + } + return aBuf.makeStringAndClear(); + } + return rStr; +} +void ScInterpreter::ScLeftB() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 2 ) ) + return; + + sal_Int32 n; + if (nParamCount == 2) + { + n = GetStringPositionArgument(); + if (n < 0) + { + PushIllegalArgument(); + return ; + } + } + else + n = 1; + OUString aStr(lcl_LeftB(GetString().getString(), n)); + PushString( aStr ); +} +void ScInterpreter::ScMidB() +{ + if ( !MustHaveParamCount( GetByte(), 3 ) ) + return; + + const sal_Int32 nCount = GetStringPositionArgument(); + const sal_Int32 nStart = GetStringPositionArgument(); + OUString aStr = GetString().getString(); + if (nStart < 1 || nCount < 0) + PushIllegalArgument(); + else + { + + aStr = lcl_LeftB(aStr, nStart + nCount - 1); + sal_Int32 nCnt = getLengthB(aStr) - nStart + 1; + aStr = lcl_RightB(aStr, std::max<sal_Int32>(nCnt,0)); + PushString(aStr); + } +} + +void ScInterpreter::ScReplaceB() +{ + if ( !MustHaveParamCount( GetByte(), 4 ) ) + return; + + OUString aNewStr = GetString().getString(); + const sal_Int32 nCount = GetStringPositionArgument(); + const sal_Int32 nPos = GetStringPositionArgument(); + OUString aOldStr = GetString().getString(); + int nLen = getLengthB( aOldStr ); + if (nPos < 1.0 || nPos > nLen || nCount < 0.0 || nPos + nCount -1 > nLen) + PushIllegalArgument(); + else + { + // REPLACEB(aOldStr;nPos;nCount;aNewStr) is the same as + // LEFTB(aOldStr;nPos-1) & aNewStr & RIGHT(aOldStr;LENB(aOldStr)-(nPos - 1)-nCount) + OUString aStr1 = lcl_LeftB( aOldStr, nPos - 1 ); + OUString aStr3 = lcl_RightB( aOldStr, nLen - nPos - nCount + 1); + + PushString( aStr1 + aNewStr + aStr3 ); + } +} + +void ScInterpreter::ScFindB() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2, 3 ) ) + return; + + sal_Int32 nStart; + if ( nParamCount == 3 ) + nStart = GetStringPositionArgument(); + else + nStart = 1; + OUString aStr = GetString().getString(); + int nLen = getLengthB( aStr ); + OUString asStr = GetString().getString(); + int nsLen = getLengthB( asStr ); + if ( nStart < 1 || nStart > nLen - nsLen + 1 ) + PushIllegalArgument(); + else + { + // create a string from sStr starting at nStart + OUString aBuf = lcl_RightB( aStr, nLen - nStart + 1 ); + // search aBuf for asStr + sal_Int32 nPos = aBuf.indexOf( asStr, 0 ); + if ( nPos == -1 ) + PushNoValue(); + else + { + // obtain byte value of nPos + int nBytePos = lcl_getLengthB( aBuf, nPos ); + PushDouble( nBytePos + nStart ); + } + } +} + +void ScInterpreter::ScSearchB() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2, 3 ) ) + return; + + sal_Int32 nStart; + if ( nParamCount == 3 ) + { + nStart = GetStringPositionArgument(); + if( nStart < 1 ) + { + PushIllegalArgument(); + return; + } + } + else + nStart = 1; + OUString aStr = GetString().getString(); + sal_Int32 nLen = getLengthB( aStr ); + OUString asStr = GetString().getString(); + sal_Int32 nsLen = nStart - 1; + if( nsLen >= nLen ) + PushNoValue(); + else + { + // create a string from sStr starting at nStart + OUString aSubStr( lcl_RightB( aStr, nLen - nStart + 1 ) ); + // search aSubStr for asStr + sal_Int32 nPos = 0; + sal_Int32 nEndPos = aSubStr.getLength(); + utl::SearchParam::SearchType eSearchType = DetectSearchType( asStr, mrDoc ); + utl::SearchParam sPar( asStr, eSearchType, false, '~', false ); + utl::TextSearch sT( sPar, ScGlobal::getCharClass() ); + if ( !sT.SearchForward( aSubStr, &nPos, &nEndPos ) ) + PushNoValue(); + else + { + // obtain byte value of nPos + int nBytePos = lcl_getLengthB( aSubStr, nPos ); + PushDouble( nBytePos + nStart ); + } + } +} + +void ScInterpreter::ScRight() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 2 ) ) + return; + + sal_Int32 n; + if (nParamCount == 2) + { + n = GetStringPositionArgument(); + if (n < 0) + { + PushIllegalArgument(); + return ; + } + } + else + n = 1; + OUString aStr = GetString().getString(); + sal_Int32 nLen = aStr.getLength(); + if ( nLen <= n ) + PushString( aStr ); + else + { + sal_Int32 nIdx = nLen; + sal_Int32 nCnt = 0; + while ( nIdx > 0 && n > nCnt ) + { + aStr.iterateCodePoints( &nIdx, -1 ); + ++nCnt; + } + aStr = aStr.copy( nIdx, nLen - nIdx ); + PushString( aStr ); + } +} + +void ScInterpreter::ScSearch() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2, 3 ) ) + return; + + sal_Int32 nStart; + if (nParamCount == 3) + { + nStart = GetStringPositionArgument(); + if( nStart < 1 ) + { + PushIllegalArgument(); + return; + } + } + else + nStart = 1; + OUString sStr = GetString().getString(); + OUString SearchStr = GetString().getString(); + sal_Int32 nPos = nStart - 1; + sal_Int32 nEndPos = sStr.getLength(); + if( nPos >= nEndPos ) + PushNoValue(); + else + { + utl::SearchParam::SearchType eSearchType = DetectSearchType( SearchStr, mrDoc ); + utl::SearchParam sPar(SearchStr, eSearchType, false, '~', false); + utl::TextSearch sT( sPar, ScGlobal::getCharClass() ); + bool bBool = sT.SearchForward(sStr, &nPos, &nEndPos); + if (!bBool) + PushNoValue(); + else + { + sal_Int32 nIdx = 0; + sal_Int32 nCnt = 0; + while ( nIdx < nPos ) + { + sStr.iterateCodePoints( &nIdx ); + ++nCnt; + } + PushDouble( static_cast<double>(nCnt + 1) ); + } + } +} + +void ScInterpreter::ScRegex() +{ + const sal_uInt8 nParamCount = GetByte(); + if (!MustHaveParamCount( nParamCount, 2, 4)) + return; + + // Flags are supported only for replacement, search match flags can be + // individually and much more flexible set in the regular expression + // pattern using (?ismwx-ismwx) + bool bGlobalReplacement = false; + sal_Int32 nOccurrence = 1; // default first occurrence, if any + if (nParamCount == 4) + { + // Argument can be either string or double. + double fOccurrence; + svl::SharedString aFlagsString; + bool bDouble; + if (!IsMissing()) + bDouble = GetDoubleOrString( fOccurrence, aFlagsString); + else + { + // For an omitted argument keep the default. + PopError(); + bDouble = true; + fOccurrence = nOccurrence; + } + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + if (bDouble) + { + if (!CheckStringPositionArgument( fOccurrence)) + { + PushError( FormulaError::IllegalArgument); + return; + } + nOccurrence = static_cast<sal_Int32>(fOccurrence); + } + else + { + const OUString aFlags( aFlagsString.getString()); + // Empty flags string is valid => no flag set. + if (aFlags.getLength() > 1) + { + // Only one flag supported. + PushIllegalArgument(); + return; + } + if (aFlags.getLength() == 1) + { + if (aFlags.indexOf('g') >= 0) + bGlobalReplacement = true; + else + { + // Unsupported flag. + PushIllegalArgument(); + return; + } + } + } + } + + bool bReplacement = false; + OUString aReplacement; + if (nParamCount >= 3) + { + // A missing argument is not an empty string to replace the match. + // nOccurrence==0 forces no replacement, so simply discard the + // argument. + if (IsMissing() || nOccurrence == 0) + PopError(); + else + { + aReplacement = GetString().getString(); + bReplacement = true; + } + } + // If bGlobalReplacement==true and bReplacement==false then + // bGlobalReplacement is silently ignored. + + const OUString aExpression = GetString().getString(); + const OUString aText = GetString().getString(); + + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + + // 0-th match or replacement is none, return original string early. + if (nOccurrence == 0) + { + PushString( aText); + return; + } + + const icu::UnicodeString aIcuExpression( + false, reinterpret_cast<const UChar*>(aExpression.getStr()), aExpression.getLength()); + UErrorCode status = U_ZERO_ERROR; + icu::RegexMatcher aRegexMatcher( aIcuExpression, 0, status); + if (U_FAILURE(status)) + { + // Invalid regex. + PushIllegalArgument(); + return; + } + // Guard against pathological patterns, limit steps of engine, see + // https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1RegexMatcher.html#a6ebcfcab4fe6a38678c0291643a03a00 + aRegexMatcher.setTimeLimit( 23*1000, status); + + const icu::UnicodeString aIcuText(false, reinterpret_cast<const UChar*>(aText.getStr()), aText.getLength()); + aRegexMatcher.reset( aIcuText); + + if (!bReplacement) + { + // Find n-th occurrence. + sal_Int32 nCount = 0; + while (aRegexMatcher.find(status) && U_SUCCESS(status) && ++nCount < nOccurrence) + ; + if (U_FAILURE(status)) + { + // Some error. + PushIllegalArgument(); + return; + } + // n-th match found? + if (nCount != nOccurrence) + { + PushError( FormulaError::NotAvailable); + return; + } + // Extract matched text. + icu::UnicodeString aMatch( aRegexMatcher.group( status)); + if (U_FAILURE(status)) + { + // Some error. + PushIllegalArgument(); + return; + } + OUString aResult( reinterpret_cast<const sal_Unicode*>(aMatch.getBuffer()), aMatch.length()); + PushString( aResult); + return; + } + + const icu::UnicodeString aIcuReplacement( + false, reinterpret_cast<const UChar*>(aReplacement.getStr()), aReplacement.getLength()); + icu::UnicodeString aReplaced; + if (bGlobalReplacement) + // Replace all occurrences of match with replacement. + aReplaced = aRegexMatcher.replaceAll( aIcuReplacement, status); + else if (nOccurrence == 1) + // Replace first occurrence of match with replacement. + aReplaced = aRegexMatcher.replaceFirst( aIcuReplacement, status); + else + { + // Replace n-th occurrence of match with replacement. + sal_Int32 nCount = 0; + while (aRegexMatcher.find(status) && U_SUCCESS(status)) + { + // XXX NOTE: After several RegexMatcher::find() the + // RegexMatcher::appendReplacement() still starts at the + // beginning (or after the last appendReplacement() position + // which is none here) and copies the original text up to the + // current found match and then replaces the found match. + if (++nCount == nOccurrence) + { + aRegexMatcher.appendReplacement( aReplaced, aIcuReplacement, status); + break; + } + } + aRegexMatcher.appendTail( aReplaced); + } + if (U_FAILURE(status)) + { + // Some error, e.g. extraneous $1 without group. + PushIllegalArgument(); + return; + } + OUString aResult( reinterpret_cast<const sal_Unicode*>(aReplaced.getBuffer()), aReplaced.length()); + PushString( aResult); +} + +void ScInterpreter::ScMid() +{ + if ( !MustHaveParamCount( GetByte(), 3 ) ) + return; + + const sal_Int32 nSubLen = GetStringPositionArgument(); + const sal_Int32 nStart = GetStringPositionArgument(); + OUString aStr = GetString().getString(); + if ( nStart < 1 || nSubLen < 0 ) + PushIllegalArgument(); + else if (nStart > kScInterpreterMaxStrLen || nSubLen > kScInterpreterMaxStrLen) + PushError(FormulaError::StringOverflow); + else + { + sal_Int32 nLen = aStr.getLength(); + sal_Int32 nIdx = 0; + sal_Int32 nCnt = 0; + while ( nIdx < nLen && nStart - 1 > nCnt ) + { + aStr.iterateCodePoints( &nIdx ); + ++nCnt; + } + sal_Int32 nIdx0 = nIdx; //start position + + while ( nIdx < nLen && nStart + nSubLen - 1 > nCnt ) + { + aStr.iterateCodePoints( &nIdx ); + ++nCnt; + } + aStr = aStr.copy( nIdx0, nIdx - nIdx0 ); + PushString( aStr ); + } +} + +void ScInterpreter::ScText() +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + OUString sFormatString = GetString().getString(); + svl::SharedString aStr; + bool bString = false; + double fVal = 0.0; + switch (GetStackType()) + { + case svError: + PopError(); + break; + case svDouble: + fVal = PopDouble(); + break; + default: + { + FormulaConstTokenRef xTok( PopToken()); + if (nGlobalError == FormulaError::NONE) + { + PushTokenRef( xTok); + // Temporarily override the ConvertStringToValue() + // error for GetCellValue() / GetCellValueOrZero() + FormulaError nSErr = mnStringNoValueError; + mnStringNoValueError = FormulaError::NotNumericString; + fVal = GetDouble(); + mnStringNoValueError = nSErr; + if (nGlobalError == FormulaError::NotNumericString) + { + // Not numeric. + nGlobalError = FormulaError::NONE; + PushTokenRef( xTok); + aStr = GetString(); + bString = true; + } + } + } + } + if (nGlobalError != FormulaError::NONE) + PushError( nGlobalError); + else if (sFormatString.isEmpty()) + { + // Mimic the Excel behaviour that + // * anything numeric returns an empty string + // * text convertible to numeric returns an empty string + // * any other text returns that text + // Conversion was detected above. + if (bString) + PushString( aStr); + else + PushString( OUString()); + } + else + { + OUString aResult; + const Color* pColor = nullptr; + LanguageType eCellLang; + const ScPatternAttr* pPattern = mrDoc.GetPattern( + aPos.Col(), aPos.Row(), aPos.Tab() ); + if ( pPattern ) + eCellLang = pPattern->GetItem( ATTR_LANGUAGE_FORMAT ).GetValue(); + else + eCellLang = ScGlobal::eLnge; + if (bString) + { + if (!pFormatter->GetPreviewString( sFormatString, aStr.getString(), + aResult, &pColor, eCellLang)) + PushIllegalArgument(); + else + PushString( aResult); + } + else + { + if (!pFormatter->GetPreviewStringGuess( sFormatString, fVal, + aResult, &pColor, eCellLang)) + PushIllegalArgument(); + else + PushString( aResult); + } + } +} + +void ScInterpreter::ScSubstitute() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 3, 4 ) ) + return; + + sal_Int32 nCnt; + if (nParamCount == 4) + { + nCnt = GetStringPositionArgument(); + if (nCnt < 1) + { + PushIllegalArgument(); + return; + } + } + else + nCnt = 0; + OUString sNewStr = GetString().getString(); + OUString sOldStr = GetString().getString(); + OUString sStr = GetString().getString(); + sal_Int32 nPos = 0; + sal_Int32 nCount = 0; + std::optional<OUStringBuffer> oResult; + for (sal_Int32 nEnd = sStr.indexOf(sOldStr); nEnd >= 0; nEnd = sStr.indexOf(sOldStr, nEnd)) + { + if (nCnt == 0 || ++nCount == nCnt) // Found a replacement cite + { + if (!oResult) // Only allocate buffer when needed + oResult.emplace(sStr.getLength() + sNewStr.getLength() - sOldStr.getLength()); + + oResult->append(sStr.subView(nPos, nEnd - nPos)); // Copy leading unchanged text + if (!CheckStringResultLen(*oResult, sNewStr.getLength())) + return PushError(GetError()); + oResult->append(sNewStr); // Copy the replacement + nPos = nEnd + sOldStr.getLength(); + if (nCnt > 0) // Found the single replacement site - end the loop + break; + } + nEnd += sOldStr.getLength(); + } + if (oResult) // If there were prior replacements, copy the rest, otherwise use original + oResult->append(sStr.subView(nPos, sStr.getLength() - nPos)); + PushString(oResult ? oResult->makeStringAndClear() : sStr); +} + +void ScInterpreter::ScRept() +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + sal_Int32 nCnt = GetStringPositionArgument(); + OUString aStr = GetString().getString(); + if (nCnt < 0) + PushIllegalArgument(); + else if (static_cast<double>(nCnt) * aStr.getLength() > kScInterpreterMaxStrLen) + { + PushError( FormulaError::StringOverflow ); + } + else if (nCnt == 0) + PushString( OUString() ); + else + { + const sal_Int32 nLen = aStr.getLength(); + OUStringBuffer aRes(nCnt*nLen); + while( nCnt-- ) + aRes.append(aStr); + PushString( aRes.makeStringAndClear() ); + } +} + +void ScInterpreter::ScConcat() +{ + sal_uInt8 nParamCount = GetByte(); + + //reverse order of parameter stack to simplify processing + ReverseStack(nParamCount); + + OUStringBuffer aRes; + while( nParamCount-- > 0) + { + OUString aStr = GetString().getString(); + if (CheckStringResultLen(aRes, aStr.getLength())) + aRes.append(aStr); + else + break; + } + PushString( aRes.makeStringAndClear() ); +} + +FormulaError ScInterpreter::GetErrorType() +{ + FormulaError nErr; + FormulaError nOldError = nGlobalError; + nGlobalError = FormulaError::NONE; + switch ( GetStackType() ) + { + case svRefList : + { + FormulaConstTokenRef x = PopToken(); + if (nGlobalError != FormulaError::NONE) + nErr = nGlobalError; + else + { + const ScRefList* pRefList = x->GetRefList(); + size_t n = pRefList->size(); + if (!n) + nErr = FormulaError::NoRef; + else if (n > 1) + nErr = FormulaError::NoValue; + else + { + ScRange aRange; + DoubleRefToRange( (*pRefList)[0], aRange); + if (nGlobalError != FormulaError::NONE) + nErr = nGlobalError; + else + { + ScAddress aAdr; + if ( DoubleRefToPosSingleRef( aRange, aAdr ) ) + nErr = mrDoc.GetErrCode( aAdr ); + else + nErr = nGlobalError; + } + } + } + } + break; + case svDoubleRef : + { + ScRange aRange; + PopDoubleRef( aRange ); + if ( nGlobalError != FormulaError::NONE ) + nErr = nGlobalError; + else + { + ScAddress aAdr; + if ( DoubleRefToPosSingleRef( aRange, aAdr ) ) + nErr = mrDoc.GetErrCode( aAdr ); + else + nErr = nGlobalError; + } + } + break; + case svSingleRef : + { + ScAddress aAdr; + PopSingleRef( aAdr ); + if ( nGlobalError != FormulaError::NONE ) + nErr = nGlobalError; + else + nErr = mrDoc.GetErrCode( aAdr ); + } + break; + default: + PopError(); + nErr = nGlobalError; + } + nGlobalError = nOldError; + return nErr; +} + +void ScInterpreter::ScErrorType() +{ + FormulaError nErr = GetErrorType(); + if ( nErr != FormulaError::NONE ) + { + nGlobalError = FormulaError::NONE; + PushDouble( static_cast<double>(nErr) ); + } + else + { + PushNA(); + } +} + +void ScInterpreter::ScErrorType_ODF() +{ + FormulaError nErr = GetErrorType(); + sal_uInt16 nErrType; + + switch ( nErr ) + { + case FormulaError::NoCode : // #NULL! + nErrType = 1; + break; + case FormulaError::DivisionByZero : // #DIV/0! + nErrType = 2; + break; + case FormulaError::NoValue : // #VALUE! + nErrType = 3; + break; + case FormulaError::NoRef : // #REF! + nErrType = 4; + break; + case FormulaError::NoName : // #NAME? + nErrType = 5; + break; + case FormulaError::IllegalFPOperation : // #NUM! + nErrType = 6; + break; + case FormulaError::NotAvailable : // #N/A + nErrType = 7; + break; + /* + #GETTING_DATA is a message that can appear in Excel when a large or + complex worksheet is being calculated. In Excel 2007 and newer, + operations are grouped so more complicated cells may finish after + earlier ones do. While the calculations are still processing, the + unfinished cells may display #GETTING_DATA. + Because the message is temporary and disappears when the calculations + complete, this isn’t a true error. + No calc error code known (yet). + + case : // GETTING_DATA + nErrType = 8; + break; + */ + default : + nErrType = 0; + break; + } + + if ( nErrType ) + { + nGlobalError =FormulaError::NONE; + PushDouble( nErrType ); + } + else + PushNA(); +} + +static bool MayBeRegExp( std::u16string_view rStr ) +{ + if ( rStr.empty() || (rStr.size() == 1 && rStr[0] != '.') ) + return false; // single meta characters can not be a regexp + // First two characters are wildcard '?' and '*' characters. + std::u16string_view cre(u"?*+.[]^$\\<>()|"); + return rStr.find_first_of(cre) != std::u16string_view::npos; +} + +static bool MayBeWildcard( std::u16string_view rStr ) +{ + // Wildcards with '~' escape, if there are no wildcards then an escaped + // character does not make sense, but it modifies the search pattern in an + // Excel compatible wildcard search... + std::u16string_view cw(u"*?~"); + return rStr.find_first_of(cw) != std::u16string_view::npos; +} + +utl::SearchParam::SearchType ScInterpreter::DetectSearchType( std::u16string_view rStr, const ScDocument& rDoc ) +{ + const auto eType = rDoc.GetDocOptions().GetFormulaSearchType(); + if ((eType == utl::SearchParam::SearchType::Wildcard && MayBeWildcard(rStr)) + || (eType == utl::SearchParam::SearchType::Regexp && MayBeRegExp(rStr))) + return eType; + return utl::SearchParam::SearchType::Normal; +} + +static bool lcl_LookupQuery( ScAddress & o_rResultPos, ScDocument& rDoc, ScInterpreterContext& rContext, + const ScQueryParam & rParam, const ScQueryEntry & rEntry, const ScFormulaCell* cell, + const ScComplexRefData* refData ) +{ + if (rEntry.eOp != SC_EQUAL) + { + // range lookup <= or >= + SCCOL nCol; + SCROW nRow; + ScQueryCellIteratorDirect aCellIter( rDoc, rContext, rParam.nTab, rParam, false); + if( aCellIter.FindEqualOrSortedLastInRange( nCol, nRow )) + { + o_rResultPos.SetCol( nCol); + o_rResultPos.SetRow( nRow); + return true; + } + } + else // EQUAL + { + if( ScQueryCellIteratorSortedCache::CanBeUsed( rDoc, rParam, rParam.nTab, cell, refData, rContext )) + { + ScQueryCellIteratorSortedCache aCellIter( rDoc, rContext, rParam.nTab, rParam, false); + if (aCellIter.GetFirst()) + { + o_rResultPos.SetCol( aCellIter.GetCol()); + o_rResultPos.SetRow( aCellIter.GetRow()); + return true; + } + } + else + { + ScQueryCellIteratorDirect aCellIter( rDoc, rContext, rParam.nTab, rParam, false); + if (aCellIter.GetFirst()) + { + o_rResultPos.SetCol( aCellIter.GetCol()); + o_rResultPos.SetRow( aCellIter.GetRow()); + return true; + } + } + } + return false; +} + +// tdf#121052: +// =VLOOKUP(SearchCriterion; RangeArray; Index; Sorted) +// [SearchCriterion] is the value searched for in the first column of the array. +// [RangeArray] is the reference, which is to comprise at least two columns. +// [Index] is the number of the column in the array that contains the value to be returned. The first column has the number 1. +// +// Prerequisite of lcl_getPrevRowWithEmptyValueLookup(): +// Value referenced by [SearchCriterion] is empty. +// lcl_getPrevRowWithEmptyValueLookup() performs following checks: +// - if we run query with "exact match" mode (i.e. VLOOKUP) +// - and if we already have the same lookup done before but for another row +// which is also had empty [SearchCriterion] +// +// then +// we could say, that for current row we could reuse results of the cached call which was done for the row2 +// In this case we return row index, which is >= 0. +// +// Elsewhere +// -1 is returned, which will lead to default behavior => +// complete lookup will be done in RangeArray inside lcl_LookupQuery() method. +// +// This method was added only for speed up to avoid several useless complete +// lookups inside [RangeArray] for searching empty strings. +// +static SCROW lcl_getPrevRowWithEmptyValueLookup( const ScLookupCache& rCache, + const ScLookupCache::QueryCriteria& rCriteria, const ScQueryParam & rParam) +{ + // is lookup value empty? + const ScQueryEntry& rEntry = rParam.GetEntry(0); + const ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + if (! rItem.maString.getString().isEmpty()) + return -1; // not found + + // try to find the row index for which we have already performed lookup + // and have some result of it inside cache + return rCache.lookup( rCriteria ); +} + +bool ScInterpreter::LookupQueryWithCache( ScAddress & o_rResultPos, + const ScQueryParam & rParam, const ScComplexRefData* refData ) const +{ + bool bFound = false; + const ScQueryEntry& rEntry = rParam.GetEntry(0); + bool bColumnsMatch = (rParam.nCol1 == rEntry.nField); + OSL_ENSURE( bColumnsMatch, "ScInterpreter::LookupQueryWithCache: columns don't match"); + // At least all volatile functions that generate indirect references have + // to force non-cached lookup. + /* TODO: We could further classify volatile functions into reference + * generating and not reference generating functions to have to force less + * direct lookups here. We could even further attribute volatility per + * parameter so it would affect only the lookup range parameter. */ + if (!bColumnsMatch || GetVolatileType() != NOT_VOLATILE) + bFound = lcl_LookupQuery( o_rResultPos, mrDoc, mrContext, rParam, rEntry, pMyFormulaCell, refData); + else + { + ScRange aLookupRange( rParam.nCol1, rParam.nRow1, rParam.nTab, + rParam.nCol2, rParam.nRow2, rParam.nTab); + ScLookupCache& rCache = mrDoc.GetLookupCache( aLookupRange, &mrContext ); + ScLookupCache::QueryCriteria aCriteria( rEntry); + ScLookupCache::Result eCacheResult = rCache.lookup( o_rResultPos, + aCriteria, aPos); + + // tdf#121052: Slow load of cells with VLOOKUP with references to empty cells + // This check was added only for speed up to avoid several useless complete + // lookups inside [RangeArray] for searching empty strings. + if (eCacheResult == ScLookupCache::NOT_CACHED && aCriteria.isEmptyStringQuery()) + { + const SCROW nPrevRowWithEmptyValueLookup = lcl_getPrevRowWithEmptyValueLookup(rCache, aCriteria, rParam); + if (nPrevRowWithEmptyValueLookup >= 0) + { + // make the same lookup using cache with different row index + // (this lookup was already cached) + ScAddress aPosPrev(aPos); + aPosPrev.SetRow(nPrevRowWithEmptyValueLookup); + + eCacheResult = rCache.lookup( o_rResultPos, aCriteria, aPosPrev ); + } + } + + switch (eCacheResult) + { + case ScLookupCache::NOT_CACHED : + case ScLookupCache::CRITERIA_DIFFERENT : + bFound = lcl_LookupQuery( o_rResultPos, mrDoc, mrContext, rParam, rEntry, pMyFormulaCell, refData); + if (eCacheResult == ScLookupCache::NOT_CACHED) + rCache.insert( o_rResultPos, aCriteria, aPos, bFound); + break; + case ScLookupCache::FOUND : + bFound = true; + break; + case ScLookupCache::NOT_AVAILABLE : + ; // nothing, bFound remains FALSE + break; + } + } + return bFound; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/interpr2.cxx b/sc/source/core/tool/interpr2.cxx new file mode 100644 index 0000000000..61f88d638a --- /dev/null +++ b/sc/source/core/tool/interpr2.cxx @@ -0,0 +1,3727 @@ +/* -*- 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 <memory> +#include <interpre.hxx> + +#include <comphelper/string.hxx> +#include <o3tl/float_int_conversion.hxx> +#include <o3tl/string_view.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/linkmgr.hxx> +#include <sfx2/objsh.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <tools/duration.hxx> +#include <sal/macros.h> +#include <osl/diagnose.h> + +#include <sc.hrc> +#include <ddelink.hxx> +#include <scmatrix.hxx> +#include <formulacell.hxx> +#include <document.hxx> +#include <dociter.hxx> +#include <docsh.hxx> +#include <unitconv.hxx> +#include <hints.hxx> +#include <dpobject.hxx> +#include <tokenarray.hxx> +#include <globalnames.hxx> +#include <stlpool.hxx> +#include <stlsheet.hxx> +#include <dpcache.hxx> + +#include <com/sun/star/sheet/DataPilotFieldFilter.hpp> + +#include <string.h> + +using ::std::vector; +using namespace com::sun::star; +using namespace formula; + +#define SCdEpsilon 1.0E-7 + +// Date and Time + +double ScInterpreter::GetDateSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay, + bool bStrict ) +{ + if ( nYear < 100 && !bStrict ) + nYear = pFormatter->ExpandTwoDigitYear( nYear ); + // Do not use a default Date ctor here because it asks system time with a + // performance penalty. + sal_Int16 nY, nM, nD; + if (bStrict) + { + nY = nYear; + nM = nMonth; + nD = nDay; + } + else + { + if (nMonth > 0) + { + nY = nYear + (nMonth-1) / 12; + nM = ((nMonth-1) % 12) + 1; + } + else + { + nY = nYear + (nMonth-12) / 12; + nM = 12 - (-nMonth) % 12; + } + nD = 1; + } + Date aDate( nD, nM, nY); + if (!bStrict) + aDate.AddDays( nDay - 1 ); + if (aDate.IsValidAndGregorian()) + return static_cast<double>(aDate - pFormatter->GetNullDate()); + else + { + SetError(FormulaError::NoValue); + return 0; + } +} + +void ScInterpreter::ScGetActDate() +{ + nFuncFmtType = SvNumFormatType::DATE; + Date aActDate( Date::SYSTEM ); + tools::Long nDiff = aActDate - pFormatter->GetNullDate(); + PushDouble(static_cast<double>(nDiff)); +} + +void ScInterpreter::ScGetActTime() +{ + nFuncFmtType = SvNumFormatType::DATETIME; + DateTime aActTime( DateTime::SYSTEM ); + tools::Long nDiff = aActTime - pFormatter->GetNullDate(); + double fTime = aActTime.GetHour() / static_cast<double>(::tools::Time::hourPerDay) + + aActTime.GetMin() / static_cast<double>(::tools::Time::minutePerDay) + + aActTime.GetSec() / static_cast<double>(::tools::Time::secondPerDay) + + aActTime.GetNanoSec() / static_cast<double>(::tools::Time::nanoSecPerDay); + PushDouble( static_cast<double>(nDiff) + fTime ); +} + +void ScInterpreter::ScGetYear() +{ + Date aDate = pFormatter->GetNullDate(); + aDate.AddDays( GetFloor32()); + PushDouble( static_cast<double>(aDate.GetYear()) ); +} + +void ScInterpreter::ScGetMonth() +{ + Date aDate = pFormatter->GetNullDate(); + aDate.AddDays( GetFloor32()); + PushDouble( static_cast<double>(aDate.GetMonth()) ); +} + +void ScInterpreter::ScGetDay() +{ + Date aDate = pFormatter->GetNullDate(); + aDate.AddDays( GetFloor32()); + PushDouble(static_cast<double>(aDate.GetDay())); +} + +void ScInterpreter::ScGetMin() +{ + sal_uInt16 nHour, nMinute, nSecond; + double fFractionOfSecond; + tools::Time::GetClock( GetDouble(), nHour, nMinute, nSecond, fFractionOfSecond, 0); + PushDouble( nMinute); +} + +void ScInterpreter::ScGetSec() +{ + sal_uInt16 nHour, nMinute, nSecond; + double fFractionOfSecond; + tools::Time::GetClock( GetDouble(), nHour, nMinute, nSecond, fFractionOfSecond, 0); + if ( fFractionOfSecond >= 0.5 ) + nSecond = ( nSecond + 1 ) % 60; + PushDouble( nSecond ); + +} + +void ScInterpreter::ScGetHour() +{ + sal_uInt16 nHour, nMinute, nSecond; + double fFractionOfSecond; + tools::Time::GetClock( GetDouble(), nHour, nMinute, nSecond, fFractionOfSecond, 0); + PushDouble( nHour); +} + +void ScInterpreter::ScGetDateValue() +{ + OUString aInputString = GetString().getString(); + sal_uInt32 nFIndex = 0; // for a default country/language + double fVal; + if (pFormatter->IsNumberFormat(aInputString, nFIndex, fVal)) + { + SvNumFormatType eType = pFormatter->GetType(nFIndex); + if (eType == SvNumFormatType::DATE || eType == SvNumFormatType::DATETIME) + { + nFuncFmtType = SvNumFormatType::DATE; + PushDouble(::rtl::math::approxFloor(fVal)); + } + else + PushIllegalArgument(); + } + else + PushIllegalArgument(); +} + +void ScInterpreter::ScGetDayOfWeek() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 2 ) ) + return; + + sal_Int16 nFlag; + if (nParamCount == 2) + nFlag = GetInt16(); + else + nFlag = 1; + + Date aDate = pFormatter->GetNullDate(); + aDate.AddDays( GetFloor32()); + int nVal = static_cast<int>(aDate.GetDayOfWeek()); // MONDAY = 0 + switch (nFlag) + { + case 1: // Sunday = 1 + if (nVal == 6) + nVal = 1; + else + nVal += 2; + break; + case 2: // Monday = 1 + nVal += 1; + break; + case 3: // Monday = 0 + ; // nothing + break; + case 11: // Monday = 1 + case 12: // Tuesday = 1 + case 13: // Wednesday = 1 + case 14: // Thursday = 1 + case 15: // Friday = 1 + case 16: // Saturday = 1 + case 17: // Sunday = 1 + if (nVal < nFlag - 11) // x = nFlag - 11 = 0,1,2,3,4,5,6 + nVal += 19 - nFlag; // nVal += (8 - (nFlag - 11) = 8 - x = 8,7,6,5,4,3,2) + else + nVal -= nFlag - 12; // nVal -= ((nFlag - 11) - 1 = x - 1 = -1,0,1,2,3,4,5) + break; + default: + SetError( FormulaError::IllegalArgument); + } + PushInt( nVal ); +} + +void ScInterpreter::ScWeeknumOOo() +{ + if ( MustHaveParamCount( GetByte(), 2 ) ) + { + sal_Int16 nFlag = GetInt16(); + + Date aDate = pFormatter->GetNullDate(); + aDate.AddDays( GetFloor32()); + PushInt( static_cast<int>(aDate.GetWeekOfYear( nFlag == 1 ? SUNDAY : MONDAY ))); + } +} + +void ScInterpreter::ScGetWeekOfYear() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 2 ) ) + return; + + sal_Int16 nFlag = ( nParamCount == 1 ) ? 1 : GetInt16(); + + Date aDate = pFormatter->GetNullDate(); + aDate.AddDays( GetFloor32()); + + sal_Int32 nMinimumNumberOfDaysInWeek; + DayOfWeek eFirstDayOfWeek; + switch ( nFlag ) + { + case 1 : + eFirstDayOfWeek = SUNDAY; + nMinimumNumberOfDaysInWeek = 1; + break; + case 2 : + eFirstDayOfWeek = MONDAY; + nMinimumNumberOfDaysInWeek = 1; + break; + case 11 : + case 12 : + case 13 : + case 14 : + case 15 : + case 16 : + case 17 : + eFirstDayOfWeek = static_cast<DayOfWeek>( nFlag - 11 ); // MONDAY := 0 + nMinimumNumberOfDaysInWeek = 1; //the week containing January 1 is week 1 + break; + case 21 : + case 150 : + // ISO 8601 + eFirstDayOfWeek = MONDAY; + nMinimumNumberOfDaysInWeek = 4; + break; + default : + PushIllegalArgument(); + return; + } + PushInt( static_cast<int>(aDate.GetWeekOfYear( eFirstDayOfWeek, nMinimumNumberOfDaysInWeek )) ); +} + +void ScInterpreter::ScGetIsoWeekOfYear() +{ + if ( MustHaveParamCount( GetByte(), 1 ) ) + { + Date aDate = pFormatter->GetNullDate(); + aDate.AddDays( GetFloor32()); + PushInt( static_cast<int>(aDate.GetWeekOfYear()) ); + } +} + +void ScInterpreter::ScEasterSunday() +{ + nFuncFmtType = SvNumFormatType::DATE; + if ( !MustHaveParamCount( GetByte(), 1 ) ) + return; + + sal_Int16 nYear = GetInt16(); + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + if ( nYear < 100 ) + nYear = pFormatter->ExpandTwoDigitYear( nYear ); + if (nYear < 1583 || nYear > 9956) + { + // Valid Gregorian and maximum year constraints not met. + PushIllegalArgument(); + return; + } + // don't worry, be happy :) + int B,C,D,E,F,G,H,I,K,L,M,N,O; + N = nYear % 19; + B = int(nYear / 100); + C = nYear % 100; + D = int(B / 4); + E = B % 4; + F = int((B + 8) / 25); + G = int((B - F + 1) / 3); + H = (19 * N + B - D - G + 15) % 30; + I = int(C / 4); + K = C % 4; + L = (32 + 2 * E + 2 * I - H - K) % 7; + M = int((N + 11 * H + 22 * L) / 451); + O = H + L - 7 * M + 114; + sal_Int16 nDay = sal::static_int_cast<sal_Int16>( O % 31 + 1 ); + sal_Int16 nMonth = sal::static_int_cast<sal_Int16>( int(O / 31) ); + PushDouble( GetDateSerial( nYear, nMonth, nDay, true ) ); +} + +FormulaError ScInterpreter::GetWeekendAndHolidayMasks( + const sal_uInt8 nParamCount, const sal_uInt32 nNullDate, vector< double >& rSortArray, + bool bWeekendMask[ 7 ] ) +{ + if ( nParamCount == 4 ) + { + vector< double > nWeekendDays; + GetNumberSequenceArray( 1, nWeekendDays, false ); + if ( nGlobalError != FormulaError::NONE ) + return nGlobalError; + else + { + if ( nWeekendDays.size() != 7 ) + return FormulaError::IllegalArgument; + + // Weekend days defined by string, Sunday...Saturday + for ( int i = 0; i < 7; i++ ) + bWeekendMask[ i ] = static_cast<bool>(nWeekendDays[ ( i == 6 ? 0 : i + 1 ) ]); + } + } + else + { + for ( int i = 0; i < 7; i++ ) + bWeekendMask[ i] = false; + + bWeekendMask[ SATURDAY ] = true; + bWeekendMask[ SUNDAY ] = true; + } + + if ( nParamCount >= 3 ) + { + GetSortArray( 1, rSortArray, nullptr, true, true ); + size_t nMax = rSortArray.size(); + for ( size_t i = 0; i < nMax; i++ ) + rSortArray.at( i ) = ::rtl::math::approxFloor( rSortArray.at( i ) ) + nNullDate; + } + + return nGlobalError; +} + +FormulaError ScInterpreter::GetWeekendAndHolidayMasks_MS( + const sal_uInt8 nParamCount, const sal_uInt32 nNullDate, vector< double >& rSortArray, + bool bWeekendMask[ 7 ], bool bWorkdayFunction ) +{ + FormulaError nErr = FormulaError::NONE; + OUString aWeekendDays; + if ( nParamCount == 4 ) + { + GetSortArray( 1, rSortArray, nullptr, true, true ); + size_t nMax = rSortArray.size(); + for ( size_t i = 0; i < nMax; i++ ) + rSortArray.at( i ) = ::rtl::math::approxFloor( rSortArray.at( i ) ) + nNullDate; + } + + if ( nParamCount >= 3 ) + { + if ( IsMissing() ) + Pop(); + else + { + switch ( GetStackType() ) + { + case svDoubleRef : + case svExternalDoubleRef : + return FormulaError::NoValue; + + default : + { + double fDouble; + svl::SharedString aSharedString; + bool bDouble = GetDoubleOrString( fDouble, aSharedString); + if ( bDouble ) + { + if ( fDouble >= 1.0 && fDouble <= 17 ) + aWeekendDays = OUString::number( fDouble ); + else + return FormulaError::NoValue; + } + else + { + if ( aSharedString.isEmpty() || aSharedString.getLength() != 7 || + ( bWorkdayFunction && aSharedString.getString() == "1111111" ) ) + return FormulaError::NoValue; + else + aWeekendDays = aSharedString.getString(); + } + } + break; + } + } + } + + for ( int i = 0; i < 7; i++ ) + bWeekendMask[ i] = false; + + if ( aWeekendDays.isEmpty() ) + { + bWeekendMask[ SATURDAY ] = true; + bWeekendMask[ SUNDAY ] = true; + } + else + { + switch ( aWeekendDays.getLength() ) + { + case 1 : + // Weekend days defined by code + switch ( aWeekendDays[ 0 ] ) + { + case '1' : bWeekendMask[ SATURDAY ] = true; bWeekendMask[ SUNDAY ] = true; break; + case '2' : bWeekendMask[ SUNDAY ] = true; bWeekendMask[ MONDAY ] = true; break; + case '3' : bWeekendMask[ MONDAY ] = true; bWeekendMask[ TUESDAY ] = true; break; + case '4' : bWeekendMask[ TUESDAY ] = true; bWeekendMask[ WEDNESDAY ] = true; break; + case '5' : bWeekendMask[ WEDNESDAY ] = true; bWeekendMask[ THURSDAY ] = true; break; + case '6' : bWeekendMask[ THURSDAY ] = true; bWeekendMask[ FRIDAY ] = true; break; + case '7' : bWeekendMask[ FRIDAY ] = true; bWeekendMask[ SATURDAY ] = true; break; + default : nErr = FormulaError::IllegalArgument; break; + } + break; + case 2 : + // Weekend day defined by code + if ( aWeekendDays[ 0 ] == '1' ) + { + switch ( aWeekendDays[ 1 ] ) + { + case '1' : bWeekendMask[ SUNDAY ] = true; break; + case '2' : bWeekendMask[ MONDAY ] = true; break; + case '3' : bWeekendMask[ TUESDAY ] = true; break; + case '4' : bWeekendMask[ WEDNESDAY ] = true; break; + case '5' : bWeekendMask[ THURSDAY ] = true; break; + case '6' : bWeekendMask[ FRIDAY ] = true; break; + case '7' : bWeekendMask[ SATURDAY ] = true; break; + default : nErr = FormulaError::IllegalArgument; break; + } + } + else + nErr = FormulaError::IllegalArgument; + break; + case 7 : + // Weekend days defined by string + for ( int i = 0; i < 7 && nErr == FormulaError::NONE; i++ ) + { + switch ( aWeekendDays[ i ] ) + { + case '0' : bWeekendMask[ i ] = false; break; + case '1' : bWeekendMask[ i ] = true; break; + default : nErr = FormulaError::IllegalArgument; break; + } + } + break; + default : + nErr = FormulaError::IllegalArgument; + break; + } + } + return nErr; +} + +void ScInterpreter::ScNetWorkdays( bool bOOXML_Version ) +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2, 4 ) ) + return; + + vector<double> nSortArray; + bool bWeekendMask[ 7 ]; + const Date& rNullDate = pFormatter->GetNullDate(); + sal_uInt32 nNullDate = Date::DateToDays( rNullDate.GetDay(), rNullDate.GetMonth(), rNullDate.GetYear() ); + FormulaError nErr; + if ( bOOXML_Version ) + { + nErr = GetWeekendAndHolidayMasks_MS( nParamCount, nNullDate, + nSortArray, bWeekendMask, false ); + } + else + { + nErr = GetWeekendAndHolidayMasks( nParamCount, nNullDate, + nSortArray, bWeekendMask ); + } + if ( nErr != FormulaError::NONE ) + PushError( nErr ); + else + { + sal_uInt32 nDate2 = GetUInt32(); + sal_uInt32 nDate1 = GetUInt32(); + if (nGlobalError != FormulaError::NONE || (nDate1 > SAL_MAX_UINT32 - nNullDate) || nDate2 > (SAL_MAX_UINT32 - nNullDate)) + { + PushIllegalArgument(); + return; + } + nDate2 += nNullDate; + nDate1 += nNullDate; + + sal_Int32 nCnt = 0; + size_t nRef = 0; + bool bReverse = ( nDate1 > nDate2 ); + if ( bReverse ) + std::swap( nDate1, nDate2 ); + size_t nMax = nSortArray.size(); + while ( nDate1 <= nDate2 ) + { + if ( !bWeekendMask[ GetDayOfWeek( nDate1 ) ] ) + { + while ( nRef < nMax && nSortArray.at( nRef ) < nDate1 ) + nRef++; + if ( nRef >= nMax || nSortArray.at( nRef ) != nDate1 ) + nCnt++; + } + ++nDate1; + } + PushDouble( static_cast<double>( bReverse ? -nCnt : nCnt ) ); + } +} + +void ScInterpreter::ScWorkday_MS() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2, 4 ) ) + return; + + nFuncFmtType = SvNumFormatType::DATE; + vector<double> nSortArray; + bool bWeekendMask[ 7 ]; + const Date& rNullDate = pFormatter->GetNullDate(); + sal_uInt32 nNullDate = Date::DateToDays( rNullDate.GetDay(), rNullDate.GetMonth(), rNullDate.GetYear() ); + FormulaError nErr = GetWeekendAndHolidayMasks_MS( nParamCount, nNullDate, + nSortArray, bWeekendMask, true ); + if ( nErr != FormulaError::NONE ) + PushError( nErr ); + else + { + sal_Int32 nDays = GetFloor32(); + sal_uInt32 nDate = GetUInt32(); + if (nGlobalError != FormulaError::NONE || (nDate > SAL_MAX_UINT32 - nNullDate)) + { + PushIllegalArgument(); + return; + } + nDate += nNullDate; + + if ( !nDays ) + PushDouble( static_cast<double>( nDate - nNullDate ) ); + else + { + size_t nMax = nSortArray.size(); + if ( nDays > 0 ) + { + size_t nRef = 0; + while ( nDays ) + { + do + { + ++nDate; + } + while ( bWeekendMask[ GetDayOfWeek( nDate ) ] ); //jump over weekend day(s) + + while ( nRef < nMax && nSortArray.at( nRef ) < nDate ) + nRef++; + + if ( nRef >= nMax || nSortArray.at( nRef ) != nDate || nRef >= nMax ) + nDays--; + } + } + else + { + sal_Int16 nRef = nMax - 1; + while ( nDays ) + { + do + { + --nDate; + } + while ( bWeekendMask[ GetDayOfWeek( nDate ) ] ); //jump over weekend day(s) + + while ( nRef >= 0 && nSortArray.at( nRef ) > nDate ) + nRef--; + + if (nRef < 0 || nSortArray.at(nRef) != nDate) + nDays++; + } + } + PushDouble( static_cast<double>( nDate - nNullDate ) ); + } + } +} + +void ScInterpreter::ScGetDate() +{ + nFuncFmtType = SvNumFormatType::DATE; + if ( !MustHaveParamCount( GetByte(), 3 ) ) + return; + + sal_Int16 nDay = GetInt16(); + sal_Int16 nMonth = GetInt16(); + if (IsMissing()) + SetError( FormulaError::ParameterExpected); // Year must be given. + sal_Int16 nYear = GetInt16(); + if (nGlobalError != FormulaError::NONE || nYear < 0) + PushIllegalArgument(); + else + PushDouble(GetDateSerial(nYear, nMonth, nDay, false)); +} + +void ScInterpreter::ScGetTime() +{ + nFuncFmtType = SvNumFormatType::TIME; + if ( MustHaveParamCount( GetByte(), 3 ) ) + { + double fSec = GetDouble(); + double fMin = GetDouble(); + double fHour = GetDouble(); + double fTime = fmod( (fHour * ::tools::Time::secondPerHour) + (fMin * ::tools::Time::secondPerMinute) + fSec, DATE_TIME_FACTOR) / DATE_TIME_FACTOR; + if (fTime < 0) + PushIllegalArgument(); + else + PushDouble( fTime); + } +} + +void ScInterpreter::ScGetDiffDate() +{ + if ( MustHaveParamCount( GetByte(), 2 ) ) + { + double fDate2 = GetDouble(); + double fDate1 = GetDouble(); + PushDouble(fDate1 - fDate2); + } +} + +void ScInterpreter::ScGetDiffDate360() +{ + /* Implementation follows + * http://www.bondmarkets.com/eCommerce/SMD_Fields_030802.pdf + * Appendix B: Day-Count Bases, there are 7 different ways to calculate the + * 30-days count. That document also claims that Excel implements the "PSA + * 30" or "NASD 30" method (funny enough they also state that Excel is the + * only tool that does so). + * + * Note that the definition given in + * http://msdn.microsoft.com/library/en-us/office97/html/SEB7C.asp + * is _not_ the way how it is actually calculated by Excel (that would not + * even match any of the 7 methods mentioned above) and would result in the + * following test cases producing wrong results according to that appendix B: + * + * 28-Feb-95 31-Aug-95 181 instead of 180 + * 29-Feb-96 31-Aug-96 181 instead of 180 + * 30-Jan-96 31-Mar-96 61 instead of 60 + * 31-Jan-96 31-Mar-96 61 instead of 60 + * + * Still, there is a difference between OOoCalc and Excel: + * In Excel: + * 02-Feb-99 31-Mar-00 results in 419 + * 31-Mar-00 02-Feb-99 results in -418 + * In Calc the result is 419 respectively -419. I consider the -418 a bug in Excel. + */ + + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2, 3 ) ) + return; + + bool bFlag = nParamCount == 3 && GetBool(); + sal_Int32 nDate2 = GetFloor32(); + sal_Int32 nDate1 = GetFloor32(); + if (nGlobalError != FormulaError::NONE) + PushError( nGlobalError); + else + { + sal_Int32 nSign; + // #i84934# only for non-US European algorithm swap dates. Else + // follow Excel's meaningless extrapolation for "interoperability". + if (bFlag && (nDate2 < nDate1)) + { + nSign = nDate1; + nDate1 = nDate2; + nDate2 = nSign; + nSign = -1; + } + else + nSign = 1; + Date aDate1 = pFormatter->GetNullDate(); + aDate1.AddDays( nDate1); + Date aDate2 = pFormatter->GetNullDate(); + aDate2.AddDays( nDate2); + if (aDate1.GetDay() == 31) + aDate1.AddDays( -1); + else if (!bFlag) + { + if (aDate1.GetMonth() == 2) + { + switch ( aDate1.GetDay() ) + { + case 28 : + if ( !aDate1.IsLeapYear() ) + aDate1.SetDay(30); + break; + case 29 : + aDate1.SetDay(30); + break; + } + } + } + if (aDate2.GetDay() == 31) + { + if (!bFlag ) + { + if (aDate1.GetDay() == 30) + aDate2.AddDays( -1); + } + else + aDate2.SetDay(30); + } + PushDouble( static_cast<double>(nSign) * + ( static_cast<double>(aDate2.GetDay()) + static_cast<double>(aDate2.GetMonth()) * 30.0 + + static_cast<double>(aDate2.GetYear()) * 360.0 + - static_cast<double>(aDate1.GetDay()) - static_cast<double>(aDate1.GetMonth()) * 30.0 + - static_cast<double>(aDate1.GetYear()) * 360.0) ); + } +} + +// fdo#44456 function DATEDIF as defined in ODF1.2 (Par. 6.10.3) +void ScInterpreter::ScGetDateDif() +{ + if ( !MustHaveParamCount( GetByte(), 3 ) ) + return; + + OUString aInterval = GetString().getString(); + sal_Int32 nDate2 = GetFloor32(); + sal_Int32 nDate1 = GetFloor32(); + + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + + // Excel doesn't swap dates or return negative numbers, so don't we. + if (nDate1 > nDate2) + { + PushIllegalArgument(); + return; + } + + double dd = nDate2 - nDate1; + // Zero difference or number of days can be returned immediately. + if (dd == 0.0 || aInterval.equalsIgnoreAsciiCase( "d" )) + { + PushDouble( dd ); + return; + } + + // split dates in day, month, year for use with formats other than "d" + sal_uInt16 d1, m1, d2, m2; + sal_Int16 y1, y2; + Date aDate1( pFormatter->GetNullDate()); + aDate1.AddDays( nDate1); + y1 = aDate1.GetYear(); + m1 = aDate1.GetMonth(); + d1 = aDate1.GetDay(); + Date aDate2( pFormatter->GetNullDate()); + aDate2.AddDays( nDate2); + y2 = aDate2.GetYear(); + m2 = aDate2.GetMonth(); + d2 = aDate2.GetDay(); + + // Close the year 0 gap to calculate year difference. + if (y1 < 0 && y2 > 0) + ++y1; + else if (y1 > 0 && y2 < 0) + ++y2; + + if ( aInterval.equalsIgnoreAsciiCase( "m" ) ) + { + // Return number of months. + int md = m2 - m1 + 12 * (y2 - y1); + if (d1 > d2) + --md; + PushInt( md ); + } + else if ( aInterval.equalsIgnoreAsciiCase( "y" ) ) + { + // Return number of years. + int yd; + if ( y2 > y1 ) + { + if (m2 > m1 || (m2 == m1 && d2 >= d1)) + yd = y2 - y1; // complete years between dates + else + yd = y2 - y1 - 1; // one incomplete year + } + else + { + // Year is equal as we don't allow reversed arguments, no + // complete year between dates. + yd = 0; + } + PushInt( yd ); + } + else if ( aInterval.equalsIgnoreAsciiCase( "md" ) ) + { + // Return number of days, excluding months and years. + // This is actually the remainder of days when subtracting years + // and months from the difference of dates. Birthday-like 23 years + // and 10 months and 19 days. + + // Algorithm's roll-over behavior extracted from Excel by try and + // error... + // If day1 <= day2 then simply day2 - day1. + // If day1 > day2 then set month1 to month2-1 and year1 to + // year2(-1) and subtract dates, e.g. for 2012-01-28,2012-03-01 set + // 2012-02-28 and then (2012-03-01)-(2012-02-28) => 2 days (leap + // year). + // For 2011-01-29,2011-03-01 the non-existent 2011-02-29 rolls over + // to 2011-03-01 so the result is 0. Same for day 31 in months with + // only 30 days. + + tools::Long nd; + if (d1 <= d2) + nd = d2 - d1; + else + { + if (m2 == 1) + { + aDate1.SetYear( y2 == 1 ? -1 : y2 - 1 ); + aDate1.SetMonth( 12 ); + } + else + { + aDate1.SetYear( y2 ); + aDate1.SetMonth( m2 - 1 ); + } + aDate1.Normalize(); + nd = aDate2 - aDate1; + } + PushDouble( nd ); + } + else if ( aInterval.equalsIgnoreAsciiCase( "ym" ) ) + { + // Return number of months, excluding years. + int md = m2 - m1 + 12 * (y2 - y1); + if (d1 > d2) + --md; + md %= 12; + PushInt( md ); + } + else if ( aInterval.equalsIgnoreAsciiCase( "yd" ) ) + { + // Return number of days, excluding years. + + // Condition corresponds with "y". + if (m2 > m1 || (m2 == m1 && d2 >= d1)) + aDate1.SetYear( y2 ); + else + aDate1.SetYear( y2 - 1 ); + // XXX NOTE: Excel for the case 1988-06-22,2012-05-11 returns + // 323, whereas the result here is 324. Don't they use the leap + // year of 2012? + // http://www.cpearson.com/excel/datedif.aspx "DATEDIF And Leap + // Years" is not correct and Excel 2010 correctly returns 0 in + // both cases mentioned there. Also using year1 as mentioned + // produces incorrect results in other cases and different from + // Excel 2010. Apparently they fixed some calculations. + aDate1.Normalize(); + double fd = aDate2 - aDate1; + PushDouble( fd ); + } + else + PushIllegalArgument(); // unsupported format +} + +void ScInterpreter::ScGetTimeValue() +{ + OUString aInputString = GetString().getString(); + sal_uInt32 nFIndex = 0; // damit default Land/Spr. + double fVal; + if (pFormatter->IsNumberFormat(aInputString, nFIndex, fVal, SvNumInputOptions::LAX_TIME)) + { + SvNumFormatType eType = pFormatter->GetType(nFIndex); + if (eType == SvNumFormatType::TIME || eType == SvNumFormatType::DATETIME) + { + nFuncFmtType = SvNumFormatType::TIME; + double fDateVal = rtl::math::approxFloor(fVal); + double fTimeVal = fVal - fDateVal; + fTimeVal = ::tools::Duration(fTimeVal).GetInDays(); // force corrected + PushDouble(fTimeVal); + } + else + PushIllegalArgument(); + } + else + PushIllegalArgument(); +} + +void ScInterpreter::ScPlusMinus() +{ + double fVal = GetDouble(); + short n = 0; + if (fVal < 0.0) + n = -1; + else if (fVal > 0.0) + n = 1; + PushInt( n ); +} + +void ScInterpreter::ScAbs() +{ + PushDouble(std::abs(GetDouble())); +} + +void ScInterpreter::ScInt() +{ + PushDouble(::rtl::math::approxFloor(GetDouble())); +} + +void ScInterpreter::RoundNumber( rtl_math_RoundingMode eMode ) +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 2 ) ) + return; + + double fVal = 0.0; + if (nParamCount == 1) + fVal = ::rtl::math::round( GetDouble(), 0, eMode ); + else + { + const sal_Int16 nDec = GetInt16(); + const double fX = GetDouble(); + if (nGlobalError == FormulaError::NONE) + { + // A quite aggressive approach with 12 significant digits. + // However, using 14 or some other doesn't work because other + // values may fail, like =ROUNDDOWN(2-5E-015;13) would produce + // 2 (another example in tdf#124286). + constexpr sal_Int16 kSigDig = 12; + + if ( ( eMode == rtl_math_RoundingMode_Down || + eMode == rtl_math_RoundingMode_Up ) && + nDec < kSigDig && fmod( fX, 1.0 ) != 0.0 ) + + { + // tdf124286 : round to significant digits before rounding + // down or up to avoid unexpected rounding errors + // caused by decimal -> binary -> decimal conversion + + double fRes = fX; + // Similar to RoundSignificant() but omitting the back-scaling + // and interim integer rounding before the final rounding, + // which would result in double rounding. Instead, adjust the + // decimals and round into integer part before scaling back. + const double fTemp = floor( log10( std::abs(fRes))) + 1.0 - kSigDig; + // Avoid inaccuracy of negative powers of 10. + if (fTemp < 0.0) + fRes *= pow(10.0, -fTemp); + else + fRes /= pow(10.0, fTemp); + if (std::isfinite(fRes)) + { + // fRes is now at a decimal normalized scale. + // Truncate up-rounding to opposite direction for values + // like 0.0600000000000005 =ROUNDUP(8.06-8;2) that here now + // is 600000000000.005 and otherwise would yield 0.07 + if (eMode == rtl_math_RoundingMode_Up) + fRes = ::rtl::math::approxFloor(fRes); + fVal = ::rtl::math::round( fRes, nDec + fTemp, eMode ); + if (fTemp < 0.0) + fVal /= pow(10.0, -fTemp); + else + fVal *= pow(10.0, fTemp); + } + else + { + // Overflow. Let our round() decide if and how to round. + fVal = ::rtl::math::round( fX, nDec, eMode ); + } + } + else + fVal = ::rtl::math::round( fX, nDec, eMode ); + } + } + PushDouble(fVal); +} + +void ScInterpreter::ScRound() +{ + RoundNumber( rtl_math_RoundingMode_Corrected ); +} + +void ScInterpreter::ScRoundDown() +{ + RoundNumber( rtl_math_RoundingMode_Down ); +} + +void ScInterpreter::ScRoundUp() +{ + RoundNumber( rtl_math_RoundingMode_Up ); +} + +void ScInterpreter::RoundSignificant( double fX, double fDigits, double &fRes ) +{ + double fTemp = floor( log10( std::abs(fX) ) ) + 1.0 - fDigits; + double fIn = fX; + // Avoid inaccuracy of negative powers of 10. + if (fTemp < 0.0) + fIn *= pow(10.0, -fTemp); + else + fIn /= pow(10.0, fTemp); + // For very large fX there might be an overflow in fIn resulting in + // non-finite. rtl::math::round() handles that and it will be propagated as + // usual. + fRes = ::rtl::math::round(fIn); + if (fTemp < 0.0) + fRes /= pow(10.0, -fTemp); + else + fRes *= pow(10.0, fTemp); +} + +// tdf#105931 +void ScInterpreter::ScRoundSignificant() +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + double fDigits = ::rtl::math::approxFloor( GetDouble() ); + double fX = GetDouble(); + if ( nGlobalError != FormulaError::NONE || fDigits < 1.0 ) + { + PushIllegalArgument(); + return; + } + + if ( fX == 0.0 ) + PushDouble( 0.0 ); + else + { + double fRes; + RoundSignificant( fX, fDigits, fRes ); + PushDouble( fRes ); + } +} + +/** tdf69552 ODFF1.2 function CEILING and Excel function CEILING.MATH + In essence, the difference between the two is that ODFF-CEILING needs to + have arguments value and significance of the same sign and with + CEILING.MATH the sign of argument significance is irrevelevant. + This is why ODFF-CEILING is exported to Excel as CEILING.MATH and + CEILING.MATH is imported in Calc as CEILING.MATH + */ +void ScInterpreter::ScCeil( bool bODFF ) +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 3 ) ) + return; + + bool bAbs = nParamCount == 3 && GetBool(); + double fDec, fVal; + if ( nParamCount == 1 ) + { + fVal = GetDouble(); + fDec = ( fVal < 0 ? -1 : 1 ); + } + else + { + bool bArgumentMissing = IsMissing(); + fDec = GetDouble(); + fVal = GetDouble(); + if ( bArgumentMissing ) + fDec = ( fVal < 0 ? -1 : 1 ); + } + if ( fVal == 0 || fDec == 0.0 ) + PushInt( 0 ); + else + { + if ( bODFF && fVal * fDec < 0 ) + PushIllegalArgument(); + else + { + if ( fVal * fDec < 0.0 ) + fDec = -fDec; + + if ( !bAbs && fVal < 0.0 ) + PushDouble(::rtl::math::approxFloor( fVal / fDec ) * fDec ); + else + PushDouble(::rtl::math::approxCeil( fVal / fDec ) * fDec ); + } + } +} + +void ScInterpreter::ScCeil_MS() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2 ) ) + return; + + double fDec = GetDouble(); + double fVal = GetDouble(); + if ( fVal == 0 || fDec == 0.0 ) + PushInt(0); + else if ( fVal * fDec > 0 ) + PushDouble(::rtl::math::approxCeil( fVal / fDec ) * fDec ); + else if ( fVal < 0.0 ) + PushDouble(::rtl::math::approxFloor( fVal / -fDec ) * -fDec ); + else + PushIllegalArgument(); +} + +void ScInterpreter::ScCeil_Precise() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 2 ) ) + return; + + double fDec, fVal; + if ( nParamCount == 1 ) + { + fVal = GetDouble(); + fDec = 1.0; + } + else + { + fDec = std::abs( GetDoubleWithDefault( 1.0 )); + fVal = GetDouble(); + } + if ( fDec == 0.0 || fVal == 0.0 ) + PushInt( 0 ); + else + PushDouble(::rtl::math::approxCeil( fVal / fDec ) * fDec ); +} + +/** tdf69552 ODFF1.2 function FLOOR and Excel function FLOOR.MATH + In essence, the difference between the two is that ODFF-FLOOR needs to + have arguments value and significance of the same sign and with + FLOOR.MATH the sign of argument significance is irrevelevant. + This is why ODFF-FLOOR is exported to Excel as FLOOR.MATH and + FLOOR.MATH is imported in Calc as FLOOR.MATH + */ +void ScInterpreter::ScFloor( bool bODFF ) +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 3 ) ) + return; + + bool bAbs = ( nParamCount == 3 && GetBool() ); + double fDec, fVal; + if ( nParamCount == 1 ) + { + fVal = GetDouble(); + fDec = ( fVal < 0 ? -1 : 1 ); + } + else + { + bool bArgumentMissing = IsMissing(); + fDec = GetDouble(); + fVal = GetDouble(); + if ( bArgumentMissing ) + fDec = ( fVal < 0 ? -1 : 1 ); + } + if ( fDec == 0.0 || fVal == 0.0 ) + PushInt( 0 ); + else + { + if ( bODFF && ( fVal * fDec < 0.0 ) ) + PushIllegalArgument(); + else + { + if ( fVal * fDec < 0.0 ) + fDec = -fDec; + + if ( !bAbs && fVal < 0.0 ) + PushDouble(::rtl::math::approxCeil( fVal / fDec ) * fDec ); + else + PushDouble(::rtl::math::approxFloor( fVal / fDec ) * fDec ); + } + } +} + +void ScInterpreter::ScFloor_MS() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2 ) ) + return; + + double fDec = GetDouble(); + double fVal = GetDouble(); + + if ( fVal == 0 ) + PushInt( 0 ); + else if ( fVal * fDec > 0 ) + PushDouble(::rtl::math::approxFloor( fVal / fDec ) * fDec ); + else if ( fDec == 0 ) + PushIllegalArgument(); + else if ( fVal < 0.0 ) + PushDouble(::rtl::math::approxCeil( fVal / -fDec ) * -fDec ); + else + PushIllegalArgument(); +} + +void ScInterpreter::ScFloor_Precise() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 2 ) ) + return; + + double fDec = nParamCount == 1 ? 1.0 : std::abs( GetDoubleWithDefault( 1.0 ) ); + double fVal = GetDouble(); + if ( fDec == 0.0 || fVal == 0.0 ) + PushInt( 0 ); + else + PushDouble(::rtl::math::approxFloor( fVal / fDec ) * fDec ); +} + +void ScInterpreter::ScEven() +{ + double fVal = GetDouble(); + if (fVal < 0.0) + PushDouble(::rtl::math::approxFloor(fVal/2.0) * 2.0); + else + PushDouble(::rtl::math::approxCeil(fVal/2.0) * 2.0); +} + +void ScInterpreter::ScOdd() +{ + double fVal = GetDouble(); + if (fVal >= 0.0) + { + fVal = ::rtl::math::approxCeil(fVal); + if (fmod(fVal, 2.0) == 0.0) + ++fVal; + } + else + { + fVal = ::rtl::math::approxFloor(fVal); + if (fmod(fVal, 2.0) == 0.0) + --fVal; + } + PushDouble(fVal); +} + +void ScInterpreter::ScArcTan2() +{ + if ( MustHaveParamCount( GetByte(), 2 ) ) + { + double fVal2 = GetDouble(); + double fVal1 = GetDouble(); + PushDouble(atan2(fVal2, fVal1)); + } +} + +void ScInterpreter::ScLog() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 2 ) ) + return; + + double fBase = nParamCount == 2 ? GetDouble() : 10.0; + double fVal = GetDouble(); + if (fVal > 0.0 && fBase > 0.0 && fBase != 1.0) + PushDouble(log(fVal) / log(fBase)); + else + PushIllegalArgument(); +} + +void ScInterpreter::ScLn() +{ + double fVal = GetDouble(); + if (fVal > 0.0) + PushDouble(log(fVal)); + else + PushIllegalArgument(); +} + +void ScInterpreter::ScLog10() +{ + double fVal = GetDouble(); + if (fVal > 0.0) + PushDouble(log10(fVal)); + else + PushIllegalArgument(); +} + +void ScInterpreter::ScNPV() +{ + nFuncFmtType = SvNumFormatType::CURRENCY; + short nParamCount = GetByte(); + if ( !MustHaveParamCountMin( nParamCount, 2) ) + return; + + KahanSum fVal = 0.0; + // We turn the stack upside down! + ReverseStack( nParamCount); + if (nGlobalError == FormulaError::NONE) + { + double fCount = 1.0; + double fRate = GetDouble(); + --nParamCount; + size_t nRefInList = 0; + ScRange aRange; + while (nParamCount-- > 0) + { + switch (GetStackType()) + { + case svDouble : + { + fVal += GetDouble() / pow(1.0 + fRate, fCount); + fCount++; + } + break; + case svSingleRef : + { + ScAddress aAdr; + PopSingleRef( aAdr ); + ScRefCellValue aCell(mrDoc, aAdr); + if (!aCell.hasEmptyValue() && aCell.hasNumeric()) + { + double fCellVal = GetCellValue(aAdr, aCell); + fVal += fCellVal / pow(1.0 + fRate, fCount); + fCount++; + } + } + break; + case svDoubleRef : + case svRefList : + { + FormulaError nErr = FormulaError::NONE; + double fCellVal; + PopDoubleRef( aRange, nParamCount, nRefInList); + ScHorizontalValueIterator aValIter( mrDoc, aRange ); + while ((nErr == FormulaError::NONE) && aValIter.GetNext(fCellVal, nErr)) + { + fVal += fCellVal / pow(1.0 + fRate, fCount); + fCount++; + } + if ( nErr != FormulaError::NONE ) + SetError(nErr); + } + break; + case svMatrix : + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatrixRef pMat = GetMatrix(); + if (pMat) + { + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + if (nC == 0 || nR == 0) + { + PushIllegalArgument(); + return; + } + else + { + double fx; + for ( SCSIZE j = 0; j < nC; j++ ) + { + for (SCSIZE k = 0; k < nR; ++k) + { + if (!pMat->IsValue(j,k)) + { + PushIllegalArgument(); + return; + } + fx = pMat->GetDouble(j,k); + fVal += fx / pow(1.0 + fRate, fCount); + fCount++; + } + } + } + } + } + break; + default : SetError(FormulaError::IllegalParameter); break; + } + } + } + PushDouble(fVal.get()); +} + +void ScInterpreter::ScIRR() +{ + nFuncFmtType = SvNumFormatType::PERCENT; + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 2 ) ) + return; + double fEstimated = nParamCount == 2 ? GetDouble() : 0.1; + double fEps = 1.0; + // If it's -1 the default result for division by zero else startvalue + double x = fEstimated == -1.0 ? 0.1 : fEstimated; + double fValue; + + ScRange aRange; + ScMatrixRef pMat; + SCSIZE nC = 0; + SCSIZE nR = 0; + bool bIsMatrix = false; + switch (GetStackType()) + { + case svDoubleRef: + PopDoubleRef(aRange); + break; + case svMatrix: + case svExternalSingleRef: + case svExternalDoubleRef: + pMat = GetMatrix(); + if (pMat) + { + pMat->GetDimensions(nC, nR); + if (nC == 0 || nR == 0) + { + PushIllegalParameter(); + return; + } + bIsMatrix = true; + } + else + { + PushIllegalParameter(); + return; + } + break; + default: + { + PushIllegalParameter(); + return; + } + } + const sal_uInt16 nIterationsMax = 20; + sal_uInt16 nItCount = 0; + FormulaError nIterError = FormulaError::NONE; + while (fEps > SCdEpsilon && nItCount < nIterationsMax && nGlobalError == FormulaError::NONE) + { // Newtons method: + KahanSum fNom = 0.0; + KahanSum fDenom = 0.0; + double fCount = 0.0; + if (bIsMatrix) + { + for (SCSIZE j = 0; j < nC && nGlobalError == FormulaError::NONE; j++) + { + for (SCSIZE k = 0; k < nR; k++) + { + if (!pMat->IsValue(j, k)) + continue; + fValue = pMat->GetDouble(j, k); + if (nGlobalError != FormulaError::NONE) + break; + + fNom += fValue / pow(1.0+x,fCount); + fDenom += -fCount * fValue / pow(1.0+x,fCount+1.0); + fCount++; + } + } + } + else + { + ScValueIterator aValIter(mrContext, aRange, mnSubTotalFlags); + bool bLoop = aValIter.GetFirst(fValue, nIterError); + while (bLoop && nIterError == FormulaError::NONE) + { + fNom += fValue / pow(1.0+x,fCount); + fDenom += -fCount * fValue / pow(1.0+x,fCount+1.0); + fCount++; + + bLoop = aValIter.GetNext(fValue, nIterError); + } + SetError(nIterError); + } + double xNew = x - fNom.get() / fDenom.get(); // x(i+1) = x(i)-f(x(i))/f'(x(i)) + nItCount++; + fEps = std::abs(xNew - x); + x = xNew; + } + if (fEstimated == 0.0 && std::abs(x) < SCdEpsilon) + x = 0.0; // adjust to zero + if (fEps < SCdEpsilon) + PushDouble(x); + else + PushError( FormulaError::NoConvergence); +} + +void ScInterpreter::ScMIRR() +{ // range_of_values ; rate_invest ; rate_reinvest + nFuncFmtType = SvNumFormatType::PERCENT; + if ( !MustHaveParamCount( GetByte(), 3 ) ) + return; + + double fRate1_reinvest = GetDouble() + 1; + double fRate1_invest = GetDouble() + 1; + + ScRange aRange; + ScMatrixRef pMat; + SCSIZE nC = 0; + SCSIZE nR = 0; + bool bIsMatrix = false; + switch ( GetStackType() ) + { + case svDoubleRef : + PopDoubleRef( aRange ); + break; + case svMatrix : + case svExternalSingleRef: + case svExternalDoubleRef: + { + pMat = GetMatrix(); + if ( pMat ) + { + pMat->GetDimensions( nC, nR ); + if ( nC == 0 || nR == 0 ) + SetError( FormulaError::IllegalArgument ); + bIsMatrix = true; + } + else + SetError( FormulaError::IllegalArgument ); + } + break; + default : + SetError( FormulaError::IllegalParameter ); + break; + } + + if ( nGlobalError != FormulaError::NONE ) + PushError( nGlobalError ); + else + { + KahanSum fNPV_reinvest = 0.0; + double fPow_reinvest = 1.0; + KahanSum fNPV_invest = 0.0; + double fPow_invest = 1.0; + sal_uLong nCount = 0; + bool bHasPosValue = false; + bool bHasNegValue = false; + + if ( bIsMatrix ) + { + double fX; + for ( SCSIZE j = 0; j < nC; j++ ) + { + for ( SCSIZE k = 0; k < nR; ++k ) + { + if ( !pMat->IsValue( j, k ) ) + continue; + fX = pMat->GetDouble( j, k ); + if ( nGlobalError != FormulaError::NONE ) + break; + + if ( fX > 0.0 ) + { // reinvestments + bHasPosValue = true; + fNPV_reinvest += fX * fPow_reinvest; + } + else if ( fX < 0.0 ) + { // investments + bHasNegValue = true; + fNPV_invest += fX * fPow_invest; + } + fPow_reinvest /= fRate1_reinvest; + fPow_invest /= fRate1_invest; + nCount++; + } + } + } + else + { + ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags ); + double fCellValue; + FormulaError nIterError = FormulaError::NONE; + + bool bLoop = aValIter.GetFirst( fCellValue, nIterError ); + while( bLoop ) + { + if( fCellValue > 0.0 ) // reinvestments + { // reinvestments + bHasPosValue = true; + fNPV_reinvest += fCellValue * fPow_reinvest; + } + else if( fCellValue < 0.0 ) // investments + { // investments + bHasNegValue = true; + fNPV_invest += fCellValue * fPow_invest; + } + fPow_reinvest /= fRate1_reinvest; + fPow_invest /= fRate1_invest; + nCount++; + + bLoop = aValIter.GetNext( fCellValue, nIterError ); + } + + if ( nIterError != FormulaError::NONE ) + SetError( nIterError ); + } + if ( !( bHasPosValue && bHasNegValue ) ) + SetError( FormulaError::IllegalArgument ); + + if ( nGlobalError != FormulaError::NONE ) + PushError( nGlobalError ); + else + { + double fResult = -fNPV_reinvest.get() / fNPV_invest.get(); + fResult *= pow( fRate1_reinvest, static_cast<double>( nCount - 1 ) ); + fResult = pow( fResult, div( 1.0, (nCount - 1)) ); + PushDouble( fResult - 1.0 ); + } + } +} + +void ScInterpreter::ScISPMT() +{ // rate ; period ; total_periods ; invest + if( MustHaveParamCount( GetByte(), 4 ) ) + { + double fInvest = GetDouble(); + double fTotal = GetDouble(); + double fPeriod = GetDouble(); + double fRate = GetDouble(); + + if( nGlobalError != FormulaError::NONE ) + PushError( nGlobalError); + else + PushDouble( fInvest * fRate * (fPeriod / fTotal - 1.0) ); + } +} + +// financial functions +double ScInterpreter::ScGetPV(double fRate, double fNper, double fPmt, + double fFv, bool bPayInAdvance) +{ + double fPv; + if (fRate == 0.0) + fPv = fFv + fPmt * fNper; + else + { + if (bPayInAdvance) + fPv = (fFv * pow(1.0 + fRate, -fNper)) + + (fPmt * (1.0 - pow(1.0 + fRate, -fNper + 1.0)) / fRate) + + fPmt; + else + fPv = (fFv * pow(1.0 + fRate, -fNper)) + + (fPmt * (1.0 - pow(1.0 + fRate, -fNper)) / fRate); + } + return -fPv; +} + +void ScInterpreter::ScPV() +{ + nFuncFmtType = SvNumFormatType::CURRENCY; + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 3, 5 ) ) + return; + + bool bPayInAdvance = nParamCount == 5 && GetBool(); + double fFv = nParamCount >= 4 ? GetDouble() : 0; + double fPmt = GetDouble(); + double fNper = GetDouble(); + double fRate = GetDouble(); + PushDouble(ScGetPV(fRate, fNper, fPmt, fFv, bPayInAdvance)); +} + +void ScInterpreter::ScSYD() +{ + nFuncFmtType = SvNumFormatType::CURRENCY; + if ( MustHaveParamCount( GetByte(), 4 ) ) + { + double fPer = GetDouble(); + double fLife = GetDouble(); + double fSalvage = GetDouble(); + double fCost = GetDouble(); + double fSyd = ((fCost - fSalvage) * (fLife - fPer + 1.0)) / + ((fLife * (fLife + 1.0)) / 2.0); + PushDouble(fSyd); + } +} + +double ScInterpreter::ScGetDDB(double fCost, double fSalvage, double fLife, + double fPeriod, double fFactor) +{ + double fDdb, fRate, fOldValue, fNewValue; + fRate = fFactor / fLife; + if (fRate >= 1.0) + { + fRate = 1.0; + fOldValue = fPeriod == 1.0 ? fCost : 0; + } + else + fOldValue = fCost * pow(1.0 - fRate, fPeriod - 1.0); + fNewValue = fCost * pow(1.0 - fRate, fPeriod); + + fDdb = fNewValue < fSalvage ? fOldValue - fSalvage : fOldValue - fNewValue; + return fDdb < 0 ? 0 : fDdb; +} + +void ScInterpreter::ScDDB() +{ + nFuncFmtType = SvNumFormatType::CURRENCY; + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 4, 5 ) ) + return; + + double fFactor = nParamCount == 5 ? GetDouble() : 2.0; + double fPeriod = GetDouble(); + double fLife = GetDouble(); + double fSalvage = GetDouble(); + double fCost = GetDouble(); + if (fCost < 0.0 || fSalvage < 0.0 || fFactor <= 0.0 || fSalvage > fCost + || fPeriod < 1.0 || fPeriod > fLife) + PushIllegalArgument(); + else + PushDouble(ScGetDDB(fCost, fSalvage, fLife, fPeriod, fFactor)); +} + +void ScInterpreter::ScDB() +{ + nFuncFmtType = SvNumFormatType::CURRENCY; + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 4, 5 ) ) + return ; + double fMonths = nParamCount == 4 ? 12.0 : ::rtl::math::approxFloor(GetDouble()); + double fPeriod = GetDouble(); + double fLife = GetDouble(); + double fSalvage = GetDouble(); + double fCost = GetDouble(); + if (fMonths < 1.0 || fMonths > 12.0 || fLife > 1200.0 || fSalvage < 0.0 || + fPeriod > (fLife + 1.0) || fSalvage > fCost || fCost <= 0.0 || + fLife <= 0 || fPeriod <= 0 ) + { + PushIllegalArgument(); + return; + } + double fOffRate = 1.0 - pow(fSalvage / fCost, 1.0 / fLife); + fOffRate = ::rtl::math::approxFloor((fOffRate * 1000.0) + 0.5) / 1000.0; + double fFirstOffRate = fCost * fOffRate * fMonths / 12.0; + double fDb = 0.0; + if (::rtl::math::approxFloor(fPeriod) == 1) + fDb = fFirstOffRate; + else + { + KahanSum fSumOffRate = fFirstOffRate; + double fMin = fLife; + if (fMin > fPeriod) fMin = fPeriod; + sal_uInt16 iMax = static_cast<sal_uInt16>(::rtl::math::approxFloor(fMin)); + for (sal_uInt16 i = 2; i <= iMax; i++) + { + fDb = -(fSumOffRate - fCost).get() * fOffRate; + fSumOffRate += fDb; + } + if (fPeriod > fLife) + fDb = -(fSumOffRate - fCost).get() * fOffRate * (12.0 - fMonths) / 12.0; + } + PushDouble(fDb); +} + +double ScInterpreter::ScInterVDB(double fCost, double fSalvage, double fLife, + double fLife1, double fPeriod, double fFactor) +{ + KahanSum fVdb = 0.0; + double fIntEnd = ::rtl::math::approxCeil(fPeriod); + sal_uLong nLoopEnd = static_cast<sal_uLong>(fIntEnd); + + double fTerm, fSln = 0; // SLN: Straight-Line Depreciation + double fSalvageValue = fCost - fSalvage; + bool bNowSln = false; + + double fDdb; + sal_uLong i; + for ( i = 1; i <= nLoopEnd; i++) + { + if(!bNowSln) + { + fDdb = ScGetDDB(fCost, fSalvage, fLife, static_cast<double>(i), fFactor); + fSln = fSalvageValue/ (fLife1 - static_cast<double>(i-1)); + + if (fSln > fDdb) + { + fTerm = fSln; + bNowSln = true; + } + else + { + fTerm = fDdb; + fSalvageValue -= fDdb; + } + } + else + { + fTerm = fSln; + } + + if ( i == nLoopEnd) + fTerm *= ( fPeriod + 1.0 - fIntEnd ); + + fVdb += fTerm; + } + return fVdb.get(); +} + +void ScInterpreter::ScVDB() +{ + nFuncFmtType = SvNumFormatType::CURRENCY; + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 5, 7 ) ) + return; + + KahanSum fVdb = 0.0; + bool bNoSwitch = nParamCount == 7 && GetBool(); + double fFactor = nParamCount >= 6 ? GetDouble() : 2.0; + double fEnd = GetDouble(); + double fStart = GetDouble(); + double fLife = GetDouble(); + double fSalvage = GetDouble(); + double fCost = GetDouble(); + if (fStart < 0.0 || fEnd < fStart || fEnd > fLife || fCost < 0.0 + || fSalvage > fCost || fFactor <= 0.0) + PushIllegalArgument(); + else + { + double fIntStart = ::rtl::math::approxFloor(fStart); + double fIntEnd = ::rtl::math::approxCeil(fEnd); + sal_uLong nLoopStart = static_cast<sal_uLong>(fIntStart); + sal_uLong nLoopEnd = static_cast<sal_uLong>(fIntEnd); + + if (bNoSwitch) + { + for (sal_uLong i = nLoopStart + 1; i <= nLoopEnd; i++) + { + double fTerm = ScGetDDB(fCost, fSalvage, fLife, static_cast<double>(i), fFactor); + + //respect partial period in the Beginning/ End: + if ( i == nLoopStart+1 ) + fTerm *= ( std::min( fEnd, fIntStart + 1.0 ) - fStart ); + else if ( i == nLoopEnd ) + fTerm *= ( fEnd + 1.0 - fIntEnd ); + + fVdb += fTerm; + } + } + else + { + double fPart = 0.0; + // respect partial period in the Beginning / End: + if ( !::rtl::math::approxEqual( fStart, fIntStart ) || + !::rtl::math::approxEqual( fEnd, fIntEnd ) ) + { + if ( !::rtl::math::approxEqual( fStart, fIntStart ) ) + { + // part to be subtracted at the beginning + double fTempIntEnd = fIntStart + 1.0; + double fTempValue = fCost - + ScInterVDB( fCost, fSalvage, fLife, fLife, fIntStart, fFactor ); + fPart += ( fStart - fIntStart ) * + ScInterVDB( fTempValue, fSalvage, fLife, fLife - fIntStart, + fTempIntEnd - fIntStart, fFactor); + } + if ( !::rtl::math::approxEqual( fEnd, fIntEnd ) ) + { + // part to be subtracted at the end + double fTempIntStart = fIntEnd - 1.0; + double fTempValue = fCost - + ScInterVDB( fCost, fSalvage, fLife, fLife, fTempIntStart, fFactor ); + fPart += ( fIntEnd - fEnd ) * + ScInterVDB( fTempValue, fSalvage, fLife, fLife - fTempIntStart, + fIntEnd - fTempIntStart, fFactor); + } + } + // calculate depreciation for whole periods + fCost -= ScInterVDB( fCost, fSalvage, fLife, fLife, fIntStart, fFactor ); + fVdb = ScInterVDB( fCost, fSalvage, fLife, fLife - fIntStart, + fIntEnd - fIntStart, fFactor); + fVdb -= fPart; + } + } + PushDouble(fVdb.get()); +} + +void ScInterpreter::ScPDuration() +{ + if ( MustHaveParamCount( GetByte(), 3 ) ) + { + double fFuture = GetDouble(); + double fPresent = GetDouble(); + double fRate = GetDouble(); + if ( fFuture <= 0.0 || fPresent <= 0.0 || fRate <= 0.0 ) + PushIllegalArgument(); + else + PushDouble( std::log( fFuture / fPresent ) / std::log1p( fRate ) ); + } +} + +void ScInterpreter::ScSLN() +{ + nFuncFmtType = SvNumFormatType::CURRENCY; + if ( MustHaveParamCount( GetByte(), 3 ) ) + { + double fLife = GetDouble(); + double fSalvage = GetDouble(); + double fCost = GetDouble(); + PushDouble( div( fCost - fSalvage, fLife ) ); + } +} + +double ScInterpreter::ScGetPMT(double fRate, double fNper, double fPv, + double fFv, bool bPayInAdvance) +{ + double fPayment; + if (fRate == 0.0) + fPayment = (fPv + fFv) / fNper; + else + { + if (bPayInAdvance) // payment in advance + fPayment = (fFv + fPv * exp( fNper * ::std::log1p(fRate) ) ) * fRate / + (std::expm1( (fNper + 1) * ::std::log1p(fRate) ) - fRate); + else // payment in arrear + fPayment = (fFv + fPv * exp(fNper * ::std::log1p(fRate) ) ) * fRate / + std::expm1( fNper * ::std::log1p(fRate) ); + } + return -fPayment; +} + +void ScInterpreter::ScPMT() +{ + nFuncFmtType = SvNumFormatType::CURRENCY; + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 3, 5 ) ) + return; + bool bPayInAdvance = nParamCount == 5 && GetBool(); + double fFv = nParamCount >= 4 ? GetDouble() : 0; + double fPv = GetDouble(); + double fNper = GetDouble(); + double fRate = GetDouble(); + PushDouble(ScGetPMT(fRate, fNper, fPv, fFv, bPayInAdvance)); +} + +void ScInterpreter::ScRRI() +{ + nFuncFmtType = SvNumFormatType::PERCENT; + if ( MustHaveParamCount( GetByte(), 3 ) ) + { + double fFutureValue = GetDouble(); + double fPresentValue = GetDouble(); + double fNrOfPeriods = GetDouble(); + if ( fNrOfPeriods <= 0.0 || fPresentValue == 0.0 ) + PushIllegalArgument(); + else + PushDouble(pow(fFutureValue / fPresentValue, 1.0 / fNrOfPeriods) - 1.0); + } +} + +double ScInterpreter::ScGetFV(double fRate, double fNper, double fPmt, + double fPv, bool bPayInAdvance) +{ + double fFv; + if (fRate == 0.0) + fFv = fPv + fPmt * fNper; + else + { + double fTerm = pow(1.0 + fRate, fNper); + if (bPayInAdvance) + fFv = fPv * fTerm + fPmt*(1.0 + fRate)*(fTerm - 1.0)/fRate; + else + fFv = fPv * fTerm + fPmt*(fTerm - 1.0)/fRate; + } + return -fFv; +} + +void ScInterpreter::ScFV() +{ + nFuncFmtType = SvNumFormatType::CURRENCY; + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 3, 5 ) ) + return; + bool bPayInAdvance = nParamCount == 5 && GetBool(); + double fPv = nParamCount >= 4 ? GetDouble() : 0; + double fPmt = GetDouble(); + double fNper = GetDouble(); + double fRate = GetDouble(); + PushDouble(ScGetFV(fRate, fNper, fPmt, fPv, bPayInAdvance)); +} + +void ScInterpreter::ScNper() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 3, 5 ) ) + return; + bool bPayInAdvance = nParamCount == 5 && GetBool(); + double fFV = nParamCount >= 4 ? GetDouble() : 0; + double fPV = GetDouble(); // Present Value + double fPmt = GetDouble(); // Payment + double fRate = GetDouble(); + // Note that due to the function specification in ODFF1.2 (and Excel) the + // amount to be paid to get from fPV to fFV is fFV_+_fPV. + if ( fPV + fFV == 0.0 ) + PushDouble( 0.0 ); + else if (fRate == 0.0) + PushDouble(-(fPV + fFV)/fPmt); + else if (bPayInAdvance) + PushDouble(log(-(fRate*fFV-fPmt*(1.0+fRate))/(fRate*fPV+fPmt*(1.0+fRate))) + / std::log1p(fRate)); + else + PushDouble(log(-(fRate*fFV-fPmt)/(fRate*fPV+fPmt)) / std::log1p(fRate)); +} + +bool ScInterpreter::RateIteration( double fNper, double fPayment, double fPv, + double fFv, bool bPayType, double & fGuess ) +{ + // See also #i15090# + // Newton-Raphson method: x(i+1) = x(i) - f(x(i)) / f'(x(i)) + // This solution handles integer and non-integer values of Nper different. + // If ODFF will constraint Nper to integer, the distinction of cases can be + // removed; only the integer-part is needed then. + bool bValid = true, bFound = false; + double fX, fXnew, fTerm, fTermDerivation; + double fGeoSeries, fGeoSeriesDerivation; + const sal_uInt16 nIterationsMax = 150; + sal_uInt16 nCount = 0; + const double fEpsilonSmall = 1.0E-14; + if ( bPayType ) + { + // payment at beginning of each period + fFv = fFv - fPayment; + fPv = fPv + fPayment; + } + if (fNper == ::rtl::math::round( fNper )) + { // Nper is an integer value + fX = fGuess; + while (!bFound && nCount < nIterationsMax) + { + double fPowN, fPowNminus1; // for (1.0+fX)^Nper and (1.0+fX)^(Nper-1) + fPowNminus1 = pow( 1.0+fX, fNper-1.0); + fPowN = fPowNminus1 * (1.0+fX); + if (fX == 0.0) + { + fGeoSeries = fNper; + fGeoSeriesDerivation = fNper * (fNper-1.0)/2.0; + } + else + { + fGeoSeries = (fPowN-1.0)/fX; + fGeoSeriesDerivation = fNper * fPowNminus1 / fX - fGeoSeries / fX; + } + fTerm = fFv + fPv *fPowN+ fPayment * fGeoSeries; + fTermDerivation = fPv * fNper * fPowNminus1 + fPayment * fGeoSeriesDerivation; + if (std::abs(fTerm) < fEpsilonSmall) + bFound = true; // will catch root which is at an extreme + else + { + if (fTermDerivation == 0.0) + fXnew = fX + 1.1 * SCdEpsilon; // move away from zero slope + else + fXnew = fX - fTerm / fTermDerivation; + nCount++; + // more accuracy not possible in oscillating cases + bFound = (std::abs(fXnew - fX) < SCdEpsilon); + fX = fXnew; + } + } + // Gnumeric returns roots < -1, Excel gives an error in that cases, + // ODFF says nothing about it. Enable the statement, if you want Excel's + // behavior. + //bValid =(fX >=-1.0); + // Update 2013-06-17: Gnumeric (v1.12.2) doesn't return roots <= -1 + // anymore. + bValid = (fX > -1.0); + } + else + { // Nper is not an integer value. + fX = (fGuess < -1.0) ? -1.0 : fGuess; // start with a valid fX + while (bValid && !bFound && nCount < nIterationsMax) + { + if (fX == 0.0) + { + fGeoSeries = fNper; + fGeoSeriesDerivation = fNper * (fNper-1.0)/2.0; + } + else + { + fGeoSeries = (pow( 1.0+fX, fNper) - 1.0) / fX; + fGeoSeriesDerivation = fNper * pow( 1.0+fX, fNper-1.0) / fX - fGeoSeries / fX; + } + fTerm = fFv + fPv *pow(1.0 + fX,fNper)+ fPayment * fGeoSeries; + fTermDerivation = fPv * fNper * pow( 1.0+fX, fNper-1.0) + fPayment * fGeoSeriesDerivation; + if (std::abs(fTerm) < fEpsilonSmall) + bFound = true; // will catch root which is at an extreme + else + { + if (fTermDerivation == 0.0) + fXnew = fX + 1.1 * SCdEpsilon; // move away from zero slope + else + fXnew = fX - fTerm / fTermDerivation; + nCount++; + // more accuracy not possible in oscillating cases + bFound = (std::abs(fXnew - fX) < SCdEpsilon); + fX = fXnew; + bValid = (fX >= -1.0); // otherwise pow(1.0+fX,fNper) will fail + } + } + } + fGuess = fX; // return approximate root + return bValid && bFound; +} + +// In Calc UI it is the function RATE(Nper;Pmt;Pv;Fv;Type;Guess) +void ScInterpreter::ScRate() +{ + nFuncFmtType = SvNumFormatType::PERCENT; + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 3, 6 ) ) + return; + + // defaults for missing arguments, see ODFF spec + double fGuess = nParamCount == 6 ? GetDouble() : 0.1; + bool bDefaultGuess = nParamCount != 6; + bool bPayType = nParamCount >= 5 && GetBool(); + double fFv = nParamCount >= 4 ? GetDouble() : 0; + double fPv = GetDouble(); + double fPayment = GetDouble(); + double fNper = GetDouble(); + double fOrigGuess = fGuess; + + if (fNper <= 0.0) // constraint from ODFF spec + { + PushIllegalArgument(); + return; + } + bool bValid = RateIteration(fNper, fPayment, fPv, fFv, bPayType, fGuess); + + if (!bValid) + { + /* TODO: try also for specified guess values, not only default? As is, + * a specified 0.1 guess may be error result but a default 0.1 guess + * may succeed. On the other hand, using a different guess value than + * the specified one may not be desired, even if that didn't match. */ + if (bDefaultGuess) + { + /* TODO: this is rather ugly, instead of looping over different + * guess values and doing a Newton goal seek for each we could + * first insert the values into the RATE equation to obtain a set + * of y values and then do a bisecting goal seek, possibly using + * different algorithms. */ + double fX = fOrigGuess; + for (int nStep = 2; nStep <= 10 && !bValid; ++nStep) + { + fGuess = fX * nStep; + bValid = RateIteration( fNper, fPayment, fPv, fFv, bPayType, fGuess); + if (!bValid) + { + fGuess = fX / nStep; + bValid = RateIteration( fNper, fPayment, fPv, fFv, bPayType, fGuess); + } + } + } + if (!bValid) + SetError(FormulaError::NoConvergence); + } + PushDouble(fGuess); +} + +double ScInterpreter::ScGetIpmt(double fRate, double fPer, double fNper, double fPv, + double fFv, bool bPayInAdvance, double& fPmt) +{ + fPmt = ScGetPMT(fRate, fNper, fPv, fFv, bPayInAdvance); // for PPMT also if fPer == 1 + double fIpmt; + nFuncFmtType = SvNumFormatType::CURRENCY; + if (fPer == 1.0) + fIpmt = bPayInAdvance ? 0.0 : -fPv; + else + { + if (bPayInAdvance) + fIpmt = ScGetFV(fRate, fPer-2.0, fPmt, fPv, true) - fPmt; + else + fIpmt = ScGetFV(fRate, fPer-1.0, fPmt, fPv, false); + } + return fIpmt * fRate; +} + +void ScInterpreter::ScIpmt() +{ + nFuncFmtType = SvNumFormatType::CURRENCY; + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 4, 6 ) ) + return; + bool bPayInAdvance = nParamCount == 6 && GetBool(); + double fFv = nParamCount >= 5 ? GetDouble() : 0; + double fPv = GetDouble(); + double fNper = GetDouble(); + double fPer = GetDouble(); + double fRate = GetDouble(); + if (fPer < 1.0 || fPer > fNper) + PushIllegalArgument(); + else + { + double fPmt; + PushDouble(ScGetIpmt(fRate, fPer, fNper, fPv, fFv, bPayInAdvance, fPmt)); + } +} + +void ScInterpreter::ScPpmt() +{ + nFuncFmtType = SvNumFormatType::CURRENCY; + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 4, 6 ) ) + return; + bool bPayInAdvance = nParamCount == 6 && GetBool(); + double fFv = nParamCount >= 5 ? GetDouble() : 0; + double fPv = GetDouble(); + double fNper = GetDouble(); + double fPer = GetDouble(); + double fRate = GetDouble(); + if (fPer < 1.0 || fPer > fNper) + PushIllegalArgument(); + else + { + double fPmt; + double fInterestPer = ScGetIpmt(fRate, fPer, fNper, fPv, fFv, bPayInAdvance, fPmt); + PushDouble(fPmt - fInterestPer); + } +} + +void ScInterpreter::ScCumIpmt() +{ + nFuncFmtType = SvNumFormatType::CURRENCY; + if ( !MustHaveParamCount( GetByte(), 6 ) ) + return; + + double fFlag = GetDoubleWithDefault( -1.0 ); + double fEnd = ::rtl::math::approxFloor(GetDouble()); + double fStart = ::rtl::math::approxFloor(GetDouble()); + double fPv = GetDouble(); + double fNper = GetDouble(); + double fRate = GetDouble(); + if (fStart < 1.0 || fEnd < fStart || fRate <= 0.0 || + fEnd > fNper || fNper <= 0.0 || fPv <= 0.0 || + ( fFlag != 0.0 && fFlag != 1.0 )) + PushIllegalArgument(); + else + { + bool bPayInAdvance = static_cast<bool>(fFlag); + sal_uLong nStart = static_cast<sal_uLong>(fStart); + sal_uLong nEnd = static_cast<sal_uLong>(fEnd) ; + double fPmt = ScGetPMT(fRate, fNper, fPv, 0.0, bPayInAdvance); + KahanSum fIpmt = 0.0; + if (nStart == 1) + { + if (!bPayInAdvance) + fIpmt = -fPv; + nStart++; + } + for (sal_uLong i = nStart; i <= nEnd; i++) + { + if (bPayInAdvance) + fIpmt += ScGetFV(fRate, static_cast<double>(i-2), fPmt, fPv, true) - fPmt; + else + fIpmt += ScGetFV(fRate, static_cast<double>(i-1), fPmt, fPv, false); + } + fIpmt *= fRate; + PushDouble(fIpmt.get()); + } +} + +void ScInterpreter::ScCumPrinc() +{ + nFuncFmtType = SvNumFormatType::CURRENCY; + if ( !MustHaveParamCount( GetByte(), 6 ) ) + return; + + double fFlag = GetDoubleWithDefault( -1.0 ); + double fEnd = ::rtl::math::approxFloor(GetDouble()); + double fStart = ::rtl::math::approxFloor(GetDouble()); + double fPv = GetDouble(); + double fNper = GetDouble(); + double fRate = GetDouble(); + if (fStart < 1.0 || fEnd < fStart || fRate <= 0.0 || + fEnd > fNper || fNper <= 0.0 || fPv <= 0.0 || + ( fFlag != 0.0 && fFlag != 1.0 )) + PushIllegalArgument(); + else + { + bool bPayInAdvance = static_cast<bool>(fFlag); + double fPmt = ScGetPMT(fRate, fNper, fPv, 0.0, bPayInAdvance); + KahanSum fPpmt = 0.0; + sal_uLong nStart = static_cast<sal_uLong>(fStart); + sal_uLong nEnd = static_cast<sal_uLong>(fEnd); + if (nStart == 1) + { + fPpmt = bPayInAdvance ? fPmt : fPmt + fPv * fRate; + nStart++; + } + for (sal_uLong i = nStart; i <= nEnd; i++) + { + if (bPayInAdvance) + fPpmt += fPmt - (ScGetFV(fRate, static_cast<double>(i-2), fPmt, fPv, true) - fPmt) * fRate; + else + fPpmt += fPmt - ScGetFV(fRate, static_cast<double>(i-1), fPmt, fPv, false) * fRate; + } + PushDouble(fPpmt.get()); + } +} + +void ScInterpreter::ScEffect() +{ + nFuncFmtType = SvNumFormatType::PERCENT; + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + double fPeriods = GetDouble(); + double fNominal = GetDouble(); + if (fPeriods < 1.0 || fNominal < 0.0) + PushIllegalArgument(); + else if ( fNominal == 0.0 ) + PushDouble( 0.0 ); + else + { + fPeriods = ::rtl::math::approxFloor(fPeriods); + PushDouble(pow(1.0 + fNominal/fPeriods, fPeriods) - 1.0); + } +} + +void ScInterpreter::ScNominal() +{ + nFuncFmtType = SvNumFormatType::PERCENT; + if ( MustHaveParamCount( GetByte(), 2 ) ) + { + double fPeriods = GetDouble(); + double fEffective = GetDouble(); + if (fPeriods < 1.0 || fEffective <= 0.0) + PushIllegalArgument(); + else + { + fPeriods = ::rtl::math::approxFloor(fPeriods); + PushDouble( (pow(fEffective + 1.0, 1.0 / fPeriods) - 1.0) * fPeriods ); + } + } +} + +void ScInterpreter::ScMod() +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + double fDenom = GetDouble(); // Denominator + if ( fDenom == 0.0 ) + { + PushError(FormulaError::DivisionByZero); + return; + } + double fNum = GetDouble(); // Numerator + double fRes = ::rtl::math::approxSub( fNum, + ::rtl::math::approxFloor( fNum / fDenom ) * fDenom ); + if ( ( fDenom > 0 && fRes >= 0 && fRes < fDenom ) || + ( fDenom < 0 && fRes <= 0 && fRes > fDenom ) ) + PushDouble( fRes ); + else + PushError( FormulaError::NoValue ); +} + +void ScInterpreter::ScIntersect() +{ + formula::FormulaConstTokenRef p2nd = PopToken(); + formula::FormulaConstTokenRef p1st = PopToken(); + + if (nGlobalError != FormulaError::NONE || !p2nd || !p1st) + { + PushIllegalArgument(); + return; + } + + StackVar sv1 = p1st->GetType(); + StackVar sv2 = p2nd->GetType(); + if ((sv1 != svSingleRef && sv1 != svDoubleRef && sv1 != svRefList) || + (sv2 != svSingleRef && sv2 != svDoubleRef && sv2 != svRefList)) + { + PushIllegalArgument(); + return; + } + + const formula::FormulaToken* x1 = p1st.get(); + const formula::FormulaToken* x2 = p2nd.get(); + if (sv1 == svRefList || sv2 == svRefList) + { + // Now this is a bit nasty but it simplifies things, and having + // intersections with lists isn't too common, if at all... + // Convert a reference to list. + const formula::FormulaToken* xt[2] = { x1, x2 }; + StackVar sv[2] = { sv1, sv2 }; + // There may only be one reference; the other is necessarily a list + // Ensure converted list proper destruction + std::unique_ptr<formula::FormulaToken> p; + for (size_t i=0; i<2; ++i) + { + if (sv[i] == svSingleRef) + { + ScComplexRefData aRef; + aRef.Ref1 = aRef.Ref2 = *xt[i]->GetSingleRef(); + p.reset(new ScRefListToken); + p->GetRefList()->push_back( aRef); + xt[i] = p.get(); + } + else if (sv[i] == svDoubleRef) + { + ScComplexRefData aRef = *xt[i]->GetDoubleRef(); + p.reset(new ScRefListToken); + p->GetRefList()->push_back( aRef); + xt[i] = p.get(); + } + } + x1 = xt[0]; + x2 = xt[1]; + + ScTokenRef xRes = new ScRefListToken; + ScRefList* pRefList = xRes->GetRefList(); + for (const auto& rRef1 : *x1->GetRefList()) + { + const ScAddress& r11 = rRef1.Ref1.toAbs(mrDoc, aPos); + const ScAddress& r12 = rRef1.Ref2.toAbs(mrDoc, aPos); + for (const auto& rRef2 : *x2->GetRefList()) + { + const ScAddress& r21 = rRef2.Ref1.toAbs(mrDoc, aPos); + const ScAddress& r22 = rRef2.Ref2.toAbs(mrDoc, aPos); + SCCOL nCol1 = ::std::max( r11.Col(), r21.Col()); + SCROW nRow1 = ::std::max( r11.Row(), r21.Row()); + SCTAB nTab1 = ::std::max( r11.Tab(), r21.Tab()); + SCCOL nCol2 = ::std::min( r12.Col(), r22.Col()); + SCROW nRow2 = ::std::min( r12.Row(), r22.Row()); + SCTAB nTab2 = ::std::min( r12.Tab(), r22.Tab()); + if (nCol2 < nCol1 || nRow2 < nRow1 || nTab2 < nTab1) + ; // nothing + else + { + ScComplexRefData aRef; + aRef.InitRange( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + pRefList->push_back( aRef); + } + } + } + size_t n = pRefList->size(); + if (!n) + PushError( FormulaError::NoCode); + else if (n == 1) + { + const ScComplexRefData& rRef = (*pRefList)[0]; + if (rRef.Ref1 == rRef.Ref2) + PushTempToken( new ScSingleRefToken(mrDoc.GetSheetLimits(), rRef.Ref1)); + else + PushTempToken( new ScDoubleRefToken(mrDoc.GetSheetLimits(), rRef)); + } + else + PushTokenRef( xRes); + } + else + { + const formula::FormulaToken* pt[2] = { x1, x2 }; + StackVar sv[2] = { sv1, sv2 }; + SCCOL nC1[2], nC2[2]; + SCROW nR1[2], nR2[2]; + SCTAB nT1[2], nT2[2]; + for (size_t i=0; i<2; ++i) + { + switch (sv[i]) + { + case svSingleRef: + case svDoubleRef: + { + { + const ScAddress& r = pt[i]->GetSingleRef()->toAbs(mrDoc, aPos); + nC1[i] = r.Col(); + nR1[i] = r.Row(); + nT1[i] = r.Tab(); + } + if (sv[i] == svDoubleRef) + { + const ScAddress& r = pt[i]->GetSingleRef2()->toAbs(mrDoc, aPos); + nC2[i] = r.Col(); + nR2[i] = r.Row(); + nT2[i] = r.Tab(); + } + else + { + nC2[i] = nC1[i]; + nR2[i] = nR1[i]; + nT2[i] = nT1[i]; + } + } + break; + default: + ; // nothing, prevent compiler warning + } + } + SCCOL nCol1 = ::std::max( nC1[0], nC1[1]); + SCROW nRow1 = ::std::max( nR1[0], nR1[1]); + SCTAB nTab1 = ::std::max( nT1[0], nT1[1]); + SCCOL nCol2 = ::std::min( nC2[0], nC2[1]); + SCROW nRow2 = ::std::min( nR2[0], nR2[1]); + SCTAB nTab2 = ::std::min( nT2[0], nT2[1]); + if (nCol2 < nCol1 || nRow2 < nRow1 || nTab2 < nTab1) + PushError( FormulaError::NoCode); + else if (nCol2 == nCol1 && nRow2 == nRow1 && nTab2 == nTab1) + PushSingleRef( nCol1, nRow1, nTab1); + else + PushDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + } +} + +void ScInterpreter::ScRangeFunc() +{ + formula::FormulaConstTokenRef x2 = PopToken(); + formula::FormulaConstTokenRef x1 = PopToken(); + + if (nGlobalError != FormulaError::NONE || !x2 || !x1) + { + PushIllegalArgument(); + return; + } + // We explicitly tell extendRangeReference() to not reuse the token, + // casting const away spares two clones. + FormulaTokenRef xRes = extendRangeReference( + mrDoc.GetSheetLimits(), const_cast<FormulaToken&>(*x1), const_cast<FormulaToken&>(*x2), aPos, false); + if (!xRes) + PushIllegalArgument(); + else + PushTokenRef( xRes); +} + +void ScInterpreter::ScUnionFunc() +{ + formula::FormulaConstTokenRef p2nd = PopToken(); + formula::FormulaConstTokenRef p1st = PopToken(); + + if (nGlobalError != FormulaError::NONE || !p2nd || !p1st) + { + PushIllegalArgument(); + return; + } + + StackVar sv1 = p1st->GetType(); + StackVar sv2 = p2nd->GetType(); + if ((sv1 != svSingleRef && sv1 != svDoubleRef && sv1 != svRefList) || + (sv2 != svSingleRef && sv2 != svDoubleRef && sv2 != svRefList)) + { + PushIllegalArgument(); + return; + } + + const formula::FormulaToken* x1 = p1st.get(); + const formula::FormulaToken* x2 = p2nd.get(); + + ScTokenRef xRes; + // Append to an existing RefList if there is one. + if (sv1 == svRefList) + { + xRes = x1->Clone(); + sv1 = svUnknown; // mark as handled + } + else if (sv2 == svRefList) + { + xRes = x2->Clone(); + sv2 = svUnknown; // mark as handled + } + else + xRes = new ScRefListToken; + ScRefList* pRes = xRes->GetRefList(); + const formula::FormulaToken* pt[2] = { x1, x2 }; + StackVar sv[2] = { sv1, sv2 }; + for (size_t i=0; i<2; ++i) + { + if (pt[i] == xRes) + continue; + switch (sv[i]) + { + case svSingleRef: + { + ScComplexRefData aRef; + aRef.Ref1 = aRef.Ref2 = *pt[i]->GetSingleRef(); + pRes->push_back( aRef); + } + break; + case svDoubleRef: + pRes->push_back( *pt[i]->GetDoubleRef()); + break; + case svRefList: + { + const ScRefList* p = pt[i]->GetRefList(); + for (const auto& rRef : *p) + { + pRes->push_back( rRef); + } + } + break; + default: + ; // nothing, prevent compiler warning + } + } + ValidateRef( *pRes); // set #REF! if needed + PushTokenRef( xRes); +} + +void ScInterpreter::ScCurrent() +{ + FormulaConstTokenRef xTok( PopToken()); + if (xTok) + { + PushTokenRef( xTok); + PushTokenRef( xTok); + } + else + PushError( FormulaError::UnknownStackVariable); +} + +void ScInterpreter::ScStyle() +{ + sal_uInt8 nParamCount = GetByte(); + if (!MustHaveParamCount(nParamCount, 1, 3)) + return; + + OUString aStyle2; // Style after timer + if (nParamCount >= 3) + aStyle2 = GetString().getString(); + tools::Long nTimeOut = 0; // timeout + if (nParamCount >= 2) + nTimeOut = static_cast<tools::Long>(GetDouble()*1000.0); + OUString aStyle1 = GetString().getString(); // Style for immediate + + if (nTimeOut < 0) + nTimeOut = 0; + + // Execute request to apply style + if ( !mrDoc.IsClipOrUndo() ) + { + ScDocShell* pShell = mrDoc.GetDocumentShell(); + if (pShell) + { + // Normalize style names right here, making sure that character case is correct, + // and that we only apply anything when there's something to apply + auto pPool = mrDoc.GetStyleSheetPool(); + if (!aStyle1.isEmpty()) + { + if (auto pNewStyle = pPool->FindAutoStyle(aStyle1)) + aStyle1 = pNewStyle->GetName(); + else + aStyle1.clear(); + } + if (!aStyle2.isEmpty()) + { + if (auto pNewStyle = pPool->FindAutoStyle(aStyle2)) + aStyle2 = pNewStyle->GetName(); + else + aStyle2.clear(); + } + // notify object shell directly! + if (!aStyle1.isEmpty() || !aStyle2.isEmpty()) + { + const ScStyleSheet* pStyle = mrDoc.GetStyle(aPos.Col(), aPos.Row(), aPos.Tab()); + + const bool bNotify = !pStyle + || (!aStyle1.isEmpty() && pStyle->GetName() != aStyle1) + || (!aStyle2.isEmpty() && pStyle->GetName() != aStyle2); + if (bNotify) + { + ScRange aRange(aPos); + ScAutoStyleHint aHint(aRange, aStyle1, nTimeOut, aStyle2); + pShell->Broadcast(aHint); + } + } + } + } + + PushDouble(0.0); +} + +static ScDdeLink* lcl_GetDdeLink( const sfx2::LinkManager* pLinkMgr, + std::u16string_view rA, std::u16string_view rT, std::u16string_view rI, sal_uInt8 nM ) +{ + size_t nCount = pLinkMgr->GetLinks().size(); + for (size_t i=0; i<nCount; i++ ) + { + ::sfx2::SvBaseLink* pBase = pLinkMgr->GetLinks()[i].get(); + if (ScDdeLink* pLink = dynamic_cast<ScDdeLink*>(pBase)) + { + if ( pLink->GetAppl() == rA && + pLink->GetTopic() == rT && + pLink->GetItem() == rI && + pLink->GetMode() == nM ) + return pLink; + } + } + + return nullptr; +} + +void ScInterpreter::ScDde() +{ + // application, file, scope + // application, Topic, Item + + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 3, 4 ) ) + return; + + sal_uInt8 nMode = SC_DDE_DEFAULT; + if (nParamCount == 4) + { + sal_uInt32 nTmp = GetUInt32(); + if (nGlobalError != FormulaError::NONE || nTmp > SAL_MAX_UINT8) + { + PushIllegalArgument(); + return; + } + nMode = static_cast<sal_uInt8>(nTmp); + } + OUString aItem = GetString().getString(); + OUString aTopic = GetString().getString(); + OUString aAppl = GetString().getString(); + + if (nMode > SC_DDE_TEXT) + nMode = SC_DDE_DEFAULT; + + // temporary documents (ScFunctionAccess) have no DocShell + // and no LinkManager -> abort + + //sfx2::LinkManager* pLinkMgr = mrDoc.GetLinkManager(); + if (!mpLinkManager) + { + PushNoValue(); + return; + } + + // Need to reinterpret after loading (build links) + pArr->AddRecalcMode( ScRecalcMode::ONLOAD_LENIENT ); + + // while the link is not evaluated, idle must be disabled (to avoid circular references) + + bool bOldEnabled = mrDoc.IsIdleEnabled(); + mrDoc.EnableIdle(false); + + // Get/ Create link object + + ScDdeLink* pLink = lcl_GetDdeLink( mpLinkManager, aAppl, aTopic, aItem, nMode ); + + //TODO: Save Dde-links (in addition) more efficient at document !!!!! + // ScDdeLink* pLink = mrDoc.GetDdeLink( aAppl, aTopic, aItem ); + + bool bWasError = ( pMyFormulaCell && pMyFormulaCell->GetRawError() != FormulaError::NONE ); + + if (!pLink) + { + pLink = new ScDdeLink( mrDoc, aAppl, aTopic, aItem, nMode ); + mpLinkManager->InsertDDELink( pLink, aAppl, aTopic, aItem ); + if ( mpLinkManager->GetLinks().size() == 1 ) // the first one? + { + SfxBindings* pBindings = mrDoc.GetViewBindings(); + if (pBindings) + pBindings->Invalidate( SID_LINKS ); // Link-Manager enabled + } + + //if the document was just loaded, but the ScDdeLink entry was missing, then + //don't update this link until the links are updated in response to the users + //decision + if (!mrDoc.HasLinkFormulaNeedingCheck()) + { + //TODO: evaluate asynchron ??? + pLink->TryUpdate(); // TryUpdate doesn't call Update multiple times + } + + if (pMyFormulaCell) + { + // StartListening after the Update to avoid circular references + pMyFormulaCell->StartListening( *pLink ); + } + } + else + { + if (pMyFormulaCell) + pMyFormulaCell->StartListening( *pLink ); + } + + // If a new Error from Reschedule appears when the link is executed then reset the errorflag + + + if ( pMyFormulaCell && pMyFormulaCell->GetRawError() != FormulaError::NONE && !bWasError ) + pMyFormulaCell->SetErrCode(FormulaError::NONE); + + // check the value + + const ScMatrix* pLinkMat = pLink->GetResult(); + if (pLinkMat) + { + SCSIZE nC, nR; + pLinkMat->GetDimensions(nC, nR); + ScMatrixRef pNewMat = GetNewMat( nC, nR, /*bEmpty*/true); + if (pNewMat) + { + pLinkMat->MatCopy(*pNewMat); // copy + PushMatrix( pNewMat ); + } + else + PushIllegalArgument(); + } + else + PushNA(); + + mrDoc.EnableIdle(bOldEnabled); + mpLinkManager->CloseCachedComps(); +} + +void ScInterpreter::ScBase() +{ // Value, Base [, MinLen] + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2, 3 ) ) + return; + + static const sal_Unicode pDigits[] = { + '0','1','2','3','4','5','6','7','8','9', + 'A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + 0 + }; + static const int nDigits = SAL_N_ELEMENTS(pDigits) - 1; + sal_Int32 nMinLen; + if ( nParamCount == 3 ) + { + double fLen = ::rtl::math::approxFloor( GetDouble() ); + if ( 1.0 <= fLen && fLen < SAL_MAX_UINT16 ) + nMinLen = static_cast<sal_Int32>(fLen); + else + nMinLen = fLen == 0.0 ? 1 : 0; // 0 means error + } + else + nMinLen = 1; + double fBase = ::rtl::math::approxFloor( GetDouble() ); + double fVal = ::rtl::math::approxFloor( GetDouble() ); + double fChars = ((fVal > 0.0 && fBase > 0.0) ? + (ceil( log( fVal ) / log( fBase ) ) + 2.0) : + 2.0); + if ( fChars >= SAL_MAX_UINT16 ) + nMinLen = 0; // Error + + if ( nGlobalError == FormulaError::NONE && nMinLen && 2 <= fBase && fBase <= nDigits && 0 <= fVal ) + { + const sal_Int32 nConstBuf = 128; + sal_Unicode aBuf[nConstBuf]; + sal_Int32 nBuf = std::max<sal_Int32>( fChars, nMinLen + 1 ); + sal_Unicode* pBuf = (nBuf <= nConstBuf ? aBuf : new sal_Unicode[nBuf]); + for ( sal_Int32 j = 0; j < nBuf; ++j ) + { + pBuf[j] = '0'; + } + sal_Unicode* p = pBuf + nBuf - 1; + *p = 0; + if ( o3tl::convertsToAtMost(fVal, sal_uLong(~0)) ) + { + sal_uLong nVal = static_cast<sal_uLong>(fVal); + sal_uLong nBase = static_cast<sal_uLong>(fBase); + while ( nVal && p > pBuf ) + { + *--p = pDigits[ nVal % nBase ]; + nVal /= nBase; + } + fVal = static_cast<double>(nVal); + } + else + { + bool bDirt = false; + while ( fVal && p > pBuf ) + { +//TODO: roundoff error starting with numbers greater than 2**48 +// double fDig = ::rtl::math::approxFloor( fmod( fVal, fBase ) ); +// a little bit better: + double fInt = ::rtl::math::approxFloor( fVal / fBase ); + double fMult = fInt * fBase; +#if 0 + // =BASIS(1e308;36) => GPF with + // nDig = (size_t) ::rtl::math::approxFloor( fVal - fMult ); + // in spite off previous test if fVal >= fMult + double fDebug1 = fVal - fMult; + // fVal := 7,5975311883090e+290 + // fMult := 7,5975311883090e+290 + // fDebug1 := 1,3848924157003e+275 <- RoundOff-Error + // fVal != fMult, aber: ::rtl::math::approxEqual( fVal, fMult ) == TRUE + double fDebug2 = ::rtl::math::approxSub( fVal, fMult ); + // and ::rtl::math::approxSub( fVal, fMult ) == 0 + double fDebug3 = ( fInt ? fVal / fInt : 0.0 ); + + // Actual after strange fDebug1 and fVal < fMult is fDebug2 == fBase, but + // anyway it can't be compared, then bDirt is executed an everything is good... + + // prevent compiler warnings + (void)fDebug1; (void)fDebug2; (void)fDebug3; +#endif + size_t nDig; + if ( fVal < fMult ) + { // something is wrong there + bDirt = true; + nDig = 0; + } + else + { + double fDig = ::rtl::math::approxFloor( ::rtl::math::approxSub( fVal, fMult ) ); + if ( bDirt ) + { + bDirt = false; + --fDig; + } + if ( fDig <= 0.0 ) + nDig = 0; + else if ( fDig >= fBase ) + nDig = static_cast<size_t>(fBase) - 1; + else + nDig = static_cast<size_t>(fDig); + } + *--p = pDigits[ nDig ]; + fVal = fInt; + } + } + if ( fVal ) + PushError( FormulaError::StringOverflow ); + else + { + if ( nBuf - (p - pBuf) <= nMinLen ) + p = pBuf + nBuf - 1 - nMinLen; + PushStringBuffer( p ); + } + if ( pBuf != aBuf ) + delete [] pBuf; + } + else + PushIllegalArgument(); +} + +void ScInterpreter::ScDecimal() +{ // Text, Base + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + double fBase = ::rtl::math::approxFloor( GetDouble() ); + OUString aStr = GetString().getString(); + if ( nGlobalError == FormulaError::NONE && 2 <= fBase && fBase <= 36 ) + { + double fVal = 0.0; + int nBase = static_cast<int>(fBase); + const sal_Unicode* p = aStr.getStr(); + while ( *p == ' ' || *p == '\t' ) + p++; // strip leading white space + if ( nBase == 16 ) + { // evtl. hex-prefix stripped + if ( *p == 'x' || *p == 'X' ) + p++; + else if ( *p == '0' && (*(p+1) == 'x' || *(p+1) == 'X') ) + p += 2; + } + while ( *p ) + { + int n; + if ( '0' <= *p && *p <= '9' ) + n = *p - '0'; + else if ( 'A' <= *p && *p <= 'Z' ) + n = 10 + (*p - 'A'); + else if ( 'a' <= *p && *p <= 'z' ) + n = 10 + (*p - 'a'); + else + n = nBase; + if ( nBase <= n ) + { + if ( *(p+1) == 0 && + ( (nBase == 2 && (*p == 'b' || *p == 'B')) + ||(nBase == 16 && (*p == 'h' || *p == 'H')) ) + ) + ; // 101b and F00Dh are ok + else + { + PushIllegalArgument(); + return ; + } + } + else + fVal = fVal * fBase + n; + p++; + + } + PushDouble( fVal ); + } + else + PushIllegalArgument(); +} + +void ScInterpreter::ScConvertOOo() +{ // Value, FromUnit, ToUnit + if ( !MustHaveParamCount( GetByte(), 3 ) ) + return; + + OUString aToUnit = GetString().getString(); + OUString aFromUnit = GetString().getString(); + double fVal = GetDouble(); + if ( nGlobalError != FormulaError::NONE ) + PushError( nGlobalError); + else + { + // first of all search for the given order; if it can't be found then search for the inverse + double fConv; + if ( ScGlobal::GetUnitConverter()->GetValue( fConv, aFromUnit, aToUnit ) ) + PushDouble( fVal * fConv ); + else if ( ScGlobal::GetUnitConverter()->GetValue( fConv, aToUnit, aFromUnit ) ) + PushDouble( fVal / fConv ); + else + PushNA(); + } +} + +void ScInterpreter::ScRoman() +{ // Value [Mode] + sal_uInt8 nParamCount = GetByte(); + if( !MustHaveParamCount( nParamCount, 1, 2 ) ) + return; + + double fMode = (nParamCount == 2) ? ::rtl::math::approxFloor( GetDouble() ) : 0.0; + double fVal = ::rtl::math::approxFloor( GetDouble() ); + if( nGlobalError != FormulaError::NONE ) + PushError( nGlobalError); + else if( (fMode >= 0.0) && (fMode < 5.0) && (fVal >= 0.0) && (fVal < 4000.0) ) + { + static const sal_Unicode pChars[] = { 'M', 'D', 'C', 'L', 'X', 'V', 'I' }; + static const sal_uInt16 pValues[] = { 1000, 500, 100, 50, 10, 5, 1 }; + static const sal_uInt16 nMaxIndex = sal_uInt16(SAL_N_ELEMENTS(pValues) - 1); + + OUStringBuffer aRoman; + sal_uInt16 nVal = static_cast<sal_uInt16>(fVal); + sal_uInt16 nMode = static_cast<sal_uInt16>(fMode); + + for( sal_uInt16 i = 0; i <= nMaxIndex / 2; i++ ) + { + sal_uInt16 nIndex = 2 * i; + sal_uInt16 nDigit = nVal / pValues[ nIndex ]; + + if( (nDigit % 5) == 4 ) + { + // assert can't happen with nVal<4000 precondition + assert( ((nDigit == 4) ? (nIndex >= 1) : (nIndex >= 2))); + + sal_uInt16 nIndex2 = (nDigit == 4) ? nIndex - 1 : nIndex - 2; + sal_uInt16 nSteps = 0; + while( (nSteps < nMode) && (nIndex < nMaxIndex) ) + { + nSteps++; + if( pValues[ nIndex2 ] - pValues[ nIndex + 1 ] <= nVal ) + nIndex++; + else + nSteps = nMode; + } + aRoman.append( OUStringChar(pChars[ nIndex ]) + OUStringChar(pChars[ nIndex2 ]) ); + nVal = sal::static_int_cast<sal_uInt16>( nVal + pValues[ nIndex ] ); + nVal = sal::static_int_cast<sal_uInt16>( nVal - pValues[ nIndex2 ] ); + } + else + { + if( nDigit > 4 ) + { + // assert can't happen with nVal<4000 precondition + assert( nIndex >= 1 ); + aRoman.append( pChars[ nIndex - 1 ] ); + } + sal_Int32 nPad = nDigit % 5; + if (nPad) + { + comphelper::string::padToLength(aRoman, aRoman.getLength() + nPad, + pChars[nIndex]); + } + nVal %= pValues[ nIndex ]; + } + } + + PushString( aRoman.makeStringAndClear() ); + } + else + PushIllegalArgument(); +} + +static bool lcl_GetArabicValue( sal_Unicode cChar, sal_uInt16& rnValue, bool& rbIsDec ) +{ + switch( cChar ) + { + case 'M': rnValue = 1000; rbIsDec = true; break; + case 'D': rnValue = 500; rbIsDec = false; break; + case 'C': rnValue = 100; rbIsDec = true; break; + case 'L': rnValue = 50; rbIsDec = false; break; + case 'X': rnValue = 10; rbIsDec = true; break; + case 'V': rnValue = 5; rbIsDec = false; break; + case 'I': rnValue = 1; rbIsDec = true; break; + default: return false; + } + return true; +} + +void ScInterpreter::ScArabic() +{ + OUString aRoman = GetString().getString(); + if( nGlobalError != FormulaError::NONE ) + PushError( nGlobalError); + else + { + aRoman = aRoman.toAsciiUpperCase(); + + sal_uInt16 nValue = 0; + sal_uInt16 nValidRest = 3999; + sal_Int32 nCharIndex = 0; + sal_Int32 nCharCount = aRoman.getLength(); + bool bValid = true; + + while( bValid && (nCharIndex < nCharCount) ) + { + sal_uInt16 nDigit1 = 0; + sal_uInt16 nDigit2 = 0; + bool bIsDec1 = false; + bValid = lcl_GetArabicValue( aRoman[nCharIndex], nDigit1, bIsDec1 ); + if( bValid && (nCharIndex + 1 < nCharCount) ) + { + bool bIsDec2 = false; + bValid = lcl_GetArabicValue( aRoman[nCharIndex + 1], nDigit2, bIsDec2 ); + } + if( bValid ) + { + if( nDigit1 >= nDigit2 ) + { + nValue = sal::static_int_cast<sal_uInt16>( nValue + nDigit1 ); + nValidRest %= (nDigit1 * (bIsDec1 ? 5 : 2)); + bValid = (nValidRest >= nDigit1); + if( bValid ) + nValidRest = sal::static_int_cast<sal_uInt16>( nValidRest - nDigit1 ); + nCharIndex++; + } + else if( nDigit1 * 2 != nDigit2 ) + { + sal_uInt16 nDiff = nDigit2 - nDigit1; + nValue = sal::static_int_cast<sal_uInt16>( nValue + nDiff ); + bValid = (nValidRest >= nDiff); + if( bValid ) + nValidRest = nDigit1 - 1; + nCharIndex += 2; + } + else + bValid = false; + } + } + if( bValid ) + PushInt( nValue ); + else + PushIllegalArgument(); + } +} + +void ScInterpreter::ScHyperLink() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1, 2 ) ) + return; + + double fVal = 0.0; + svl::SharedString aStr; + ScMatValType nResultType = ScMatValType::String; + + if ( nParamCount == 2 ) + { + switch ( GetStackType() ) + { + case svDouble: + fVal = GetDouble(); + nResultType = ScMatValType::Value; + break; + case svString: + aStr = GetString(); + break; + case svSingleRef: + case svDoubleRef: + { + ScAddress aAdr; + if ( !PopDoubleRefOrSingleRef( aAdr ) ) + break; + + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasEmptyValue()) + nResultType = ScMatValType::Empty; + else + { + FormulaError nErr = GetCellErrCode(aCell); + if (nErr != FormulaError::NONE) + SetError( nErr); + else if (aCell.hasNumeric()) + { + fVal = GetCellValue(aAdr, aCell); + nResultType = ScMatValType::Value; + } + else + GetCellString(aStr, aCell); + } + } + break; + case svMatrix: + nResultType = GetDoubleOrStringFromMatrix( fVal, aStr); + break; + case svMissing: + case svEmptyCell: + Pop(); + // mimic xcl + fVal = 0.0; + nResultType = ScMatValType::Value; + break; + default: + PopError(); + SetError( FormulaError::IllegalArgument); + } + } + svl::SharedString aUrl = GetString(); + ScMatrixRef pResMat = GetNewMat( 1, 2); + if (nGlobalError != FormulaError::NONE) + { + fVal = CreateDoubleError( nGlobalError); + nResultType = ScMatValType::Value; + } + if (nParamCount == 2 || nGlobalError != FormulaError::NONE) + { + if (ScMatrix::IsValueType( nResultType)) + pResMat->PutDouble( fVal, 0); + else if (ScMatrix::IsRealStringType( nResultType)) + pResMat->PutString(aStr, 0); + else // EmptyType, EmptyPathType, mimic xcl + pResMat->PutDouble( 0.0, 0 ); + } + else + pResMat->PutString(aUrl, 0); + pResMat->PutString(aUrl, 1); + bMatrixFormula = true; + PushMatrix(pResMat); +} + +/** Resources at the website of the European Commission: + http://ec.europa.eu/economy_finance/euro/adoption/conversion/ + http://ec.europa.eu/economy_finance/euro/countries/ + */ +static bool lclConvertMoney( std::u16string_view aSearchUnit, double& rfRate, int& rnDec ) +{ + struct ConvertInfo + { + const char* pCurrText; + double fRate; + int nDec; + }; + static const ConvertInfo aConvertTable[] = { + { "EUR", 1.0, 2 }, + { "ATS", 13.7603, 2 }, + { "BEF", 40.3399, 0 }, + { "DEM", 1.95583, 2 }, + { "ESP", 166.386, 0 }, + { "FIM", 5.94573, 2 }, + { "FRF", 6.55957, 2 }, + { "IEP", 0.787564, 2 }, + { "ITL", 1936.27, 0 }, + { "LUF", 40.3399, 0 }, + { "NLG", 2.20371, 2 }, + { "PTE", 200.482, 2 }, + { "GRD", 340.750, 2 }, + { "SIT", 239.640, 2 }, + { "MTL", 0.429300, 2 }, + { "CYP", 0.585274, 2 }, + { "SKK", 30.1260, 2 }, + { "EEK", 15.6466, 2 }, + { "LVL", 0.702804, 2 }, + { "LTL", 3.45280, 2 }, + { "HRK", 7.53450, 2 } + }; + + for (const auto & i : aConvertTable) + if ( o3tl::equalsIgnoreAsciiCase( aSearchUnit, i.pCurrText ) ) + { + rfRate = i.fRate; + rnDec = i.nDec; + return true; + } + return false; +} + +void ScInterpreter::ScEuroConvert() +{ //Value, FromUnit, ToUnit[, FullPrecision, [TriangulationPrecision]] + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 3, 5 ) ) + return; + + double fPrecision = 0.0; + if ( nParamCount == 5 ) + { + fPrecision = ::rtl::math::approxFloor(GetDouble()); + if ( fPrecision < 3 ) + { + PushIllegalArgument(); + return; + } + } + + bool bFullPrecision = nParamCount >= 4 && GetBool(); + OUString aToUnit = GetString().getString(); + OUString aFromUnit = GetString().getString(); + double fVal = GetDouble(); + if ( nGlobalError != FormulaError::NONE ) + PushError( nGlobalError); + else + { + double fFromRate; + double fToRate; + int nFromDec; + int nToDec; + if ( lclConvertMoney( aFromUnit, fFromRate, nFromDec ) + && lclConvertMoney( aToUnit, fToRate, nToDec ) ) + { + double fRes; + if ( aFromUnit.equalsIgnoreAsciiCase( aToUnit ) ) + fRes = fVal; + else + { + if ( aFromUnit.equalsIgnoreAsciiCase( "EUR" ) ) + fRes = fVal * fToRate; + else + { + double fIntermediate = fVal / fFromRate; + if ( fPrecision ) + fIntermediate = ::rtl::math::round( fIntermediate, + static_cast<int>(fPrecision) ); + fRes = fIntermediate * fToRate; + } + if ( !bFullPrecision ) + fRes = ::rtl::math::round( fRes, nToDec ); + } + PushDouble( fRes ); + } + else + PushIllegalArgument(); + } +} + +// BAHTTEXT +#define UTF8_TH_0 "\340\270\250\340\270\271\340\270\231\340\270\242\340\271\214" +#define UTF8_TH_1 "\340\270\253\340\270\231\340\270\266\340\271\210\340\270\207" +#define UTF8_TH_2 "\340\270\252\340\270\255\340\270\207" +#define UTF8_TH_3 "\340\270\252\340\270\262\340\270\241" +#define UTF8_TH_4 "\340\270\252\340\270\265\340\271\210" +#define UTF8_TH_5 "\340\270\253\340\271\211\340\270\262" +#define UTF8_TH_6 "\340\270\253\340\270\201" +#define UTF8_TH_7 "\340\271\200\340\270\210\340\271\207\340\270\224" +#define UTF8_TH_8 "\340\271\201\340\270\233\340\270\224" +#define UTF8_TH_9 "\340\271\200\340\270\201\340\271\211\340\270\262" +#define UTF8_TH_10 "\340\270\252\340\270\264\340\270\232" +#define UTF8_TH_11 "\340\271\200\340\270\255\340\271\207\340\270\224" +#define UTF8_TH_20 "\340\270\242\340\270\265\340\271\210" +#define UTF8_TH_1E2 "\340\270\243\340\271\211\340\270\255\340\270\242" +#define UTF8_TH_1E3 "\340\270\236\340\270\261\340\270\231" +#define UTF8_TH_1E4 "\340\270\253\340\270\241\340\270\267\340\271\210\340\270\231" +#define UTF8_TH_1E5 "\340\271\201\340\270\252\340\270\231" +#define UTF8_TH_1E6 "\340\270\245\340\271\211\340\270\262\340\270\231" +#define UTF8_TH_DOT0 "\340\270\226\340\271\211\340\270\247\340\270\231" +#define UTF8_TH_BAHT "\340\270\232\340\270\262\340\270\227" +#define UTF8_TH_SATANG "\340\270\252\340\270\225\340\270\262\340\270\207\340\270\204\340\271\214" +#define UTF8_TH_MINUS "\340\270\245\340\270\232" + +// local functions +namespace { + +void lclSplitBlock( double& rfInt, sal_Int32& rnBlock, double fValue, double fSize ) +{ + rnBlock = static_cast< sal_Int32 >( modf( (fValue + 0.1) / fSize, &rfInt ) * fSize + 0.1 ); +} + +/** Appends a digit (0 to 9) to the passed string. */ +void lclAppendDigit( OStringBuffer& rText, sal_Int32 nDigit ) +{ + switch( nDigit ) + { + case 0: rText.append( UTF8_TH_0 ); break; + case 1: rText.append( UTF8_TH_1 ); break; + case 2: rText.append( UTF8_TH_2 ); break; + case 3: rText.append( UTF8_TH_3 ); break; + case 4: rText.append( UTF8_TH_4 ); break; + case 5: rText.append( UTF8_TH_5 ); break; + case 6: rText.append( UTF8_TH_6 ); break; + case 7: rText.append( UTF8_TH_7 ); break; + case 8: rText.append( UTF8_TH_8 ); break; + case 9: rText.append( UTF8_TH_9 ); break; + default: OSL_FAIL( "lclAppendDigit - illegal digit" ); + } +} + +/** Appends a value raised to a power of 10: nDigit*10^nPow10. + @param nDigit A digit in the range from 1 to 9. + @param nPow10 A value in the range from 2 to 5. + */ +void lclAppendPow10( OStringBuffer& rText, sal_Int32 nDigit, sal_Int32 nPow10 ) +{ + OSL_ENSURE( (1 <= nDigit) && (nDigit <= 9), "lclAppendPow10 - illegal digit" ); + lclAppendDigit( rText, nDigit ); + switch( nPow10 ) + { + case 2: rText.append( UTF8_TH_1E2 ); break; + case 3: rText.append( UTF8_TH_1E3 ); break; + case 4: rText.append( UTF8_TH_1E4 ); break; + case 5: rText.append( UTF8_TH_1E5 ); break; + default: OSL_FAIL( "lclAppendPow10 - illegal power" ); + } +} + +/** Appends a block of 6 digits (value from 1 to 999,999) to the passed string. */ +void lclAppendBlock( OStringBuffer& rText, sal_Int32 nValue ) +{ + OSL_ENSURE( (1 <= nValue) && (nValue <= 999999), "lclAppendBlock - illegal value" ); + if( nValue >= 100000 ) + { + lclAppendPow10( rText, nValue / 100000, 5 ); + nValue %= 100000; + } + if( nValue >= 10000 ) + { + lclAppendPow10( rText, nValue / 10000, 4 ); + nValue %= 10000; + } + if( nValue >= 1000 ) + { + lclAppendPow10( rText, nValue / 1000, 3 ); + nValue %= 1000; + } + if( nValue >= 100 ) + { + lclAppendPow10( rText, nValue / 100, 2 ); + nValue %= 100; + } + if( nValue <= 0 ) + return; + + sal_Int32 nTen = nValue / 10; + sal_Int32 nOne = nValue % 10; + if( nTen >= 1 ) + { + if( nTen >= 3 ) + lclAppendDigit( rText, nTen ); + else if( nTen == 2 ) + rText.append( UTF8_TH_20 ); + rText.append( UTF8_TH_10 ); + } + if( (nTen > 0) && (nOne == 1) ) + rText.append( UTF8_TH_11 ); + else if( nOne > 0 ) + lclAppendDigit( rText, nOne ); +} + +} // namespace + +void ScInterpreter::ScBahtText() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1 ) ) + return; + + double fValue = GetDouble(); + if( nGlobalError != FormulaError::NONE ) + { + PushError( nGlobalError); + return; + } + + // sign + bool bMinus = fValue < 0.0; + fValue = std::abs( fValue ); + + // round to 2 digits after decimal point, fValue contains Satang as integer + fValue = ::rtl::math::approxFloor( fValue * 100.0 + 0.5 ); + + // split Baht and Satang + double fBaht = 0.0; + sal_Int32 nSatang = 0; + lclSplitBlock( fBaht, nSatang, fValue, 100.0 ); + + OStringBuffer aText; + + // generate text for Baht value + if( fBaht == 0.0 ) + { + if( nSatang == 0 ) + aText.append( UTF8_TH_0 ); + } + else while( fBaht > 0.0 ) + { + OStringBuffer aBlock; + sal_Int32 nBlock = 0; + lclSplitBlock( fBaht, nBlock, fBaht, 1.0e6 ); + if( nBlock > 0 ) + lclAppendBlock( aBlock, nBlock ); + // add leading "million", if there will come more blocks + if( fBaht > 0.0 ) + aBlock.insert( 0, UTF8_TH_1E6 ); + + aText.insert(0, aBlock); + } + if (!aText.isEmpty()) + aText.append( UTF8_TH_BAHT ); + + // generate text for Satang value + if( nSatang == 0 ) + { + aText.append( UTF8_TH_DOT0 ); + } + else + { + lclAppendBlock( aText, nSatang ); + aText.append( UTF8_TH_SATANG ); + } + + // add the minus sign + if( bMinus ) + aText.insert( 0, UTF8_TH_MINUS ); + + PushString( OStringToOUString(aText, RTL_TEXTENCODING_UTF8) ); +} + +void ScInterpreter::ScGetPivotData() +{ + sal_uInt8 nParamCount = GetByte(); + + if (!MustHaveParamCountMin(nParamCount, 2) || (nParamCount % 2) == 1) + { + PushError(FormulaError::NoRef); + return; + } + + bool bOldSyntax = false; + if (nParamCount == 2) + { + // if the first parameter is a ref, assume old syntax + StackVar eFirstType = GetStackType(2); + if (eFirstType == svSingleRef || eFirstType == svDoubleRef) + bOldSyntax = true; + } + + std::vector<sheet::DataPilotFieldFilter> aFilters; + OUString aDataFieldName; + ScRange aBlock; + + if (bOldSyntax) + { + aDataFieldName = GetString().getString(); + + switch (GetStackType()) + { + case svDoubleRef : + PopDoubleRef(aBlock); + break; + case svSingleRef : + { + ScAddress aAddr; + PopSingleRef(aAddr); + aBlock = aAddr; + } + break; + default: + PushError(FormulaError::NoRef); + return; + } + } + else + { + // Standard syntax: separate name/value pairs + + sal_uInt16 nFilterCount = nParamCount / 2 - 1; + aFilters.resize(nFilterCount); + + sal_uInt16 i = nFilterCount; + while (i-- > 0) + { + /* TODO: also, in case of numeric the entire filter match should + * not be on a (even if locale independent) formatted string down + * below in pDPObj->GetPivotData(). */ + + bool bEvaluateFormatIndex; + switch (GetRawStackType()) + { + case svSingleRef: + case svDoubleRef: + bEvaluateFormatIndex = true; + break; + default: + bEvaluateFormatIndex = false; + } + + double fDouble; + svl::SharedString aSharedString; + bool bDouble = GetDoubleOrString( fDouble, aSharedString); + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + + if (bDouble) + { + sal_uInt32 nNumFormat; + if (bEvaluateFormatIndex && nCurFmtIndex) + nNumFormat = nCurFmtIndex; + else + { + if (nCurFmtType == SvNumFormatType::UNDEFINED) + nNumFormat = 0; + else + nNumFormat = pFormatter->GetStandardFormat( nCurFmtType, ScGlobal::eLnge); + } + const Color* pColor; + pFormatter->GetOutputString( fDouble, nNumFormat, aFilters[i].MatchValueName, &pColor); + aFilters[i].MatchValue = ScDPCache::GetLocaleIndependentFormattedString( + fDouble, *pFormatter, nNumFormat); + } + else + { + aFilters[i].MatchValueName = aSharedString.getString(); + + // Parse possible number from MatchValueName and format + // locale independent as MatchValue. + sal_uInt32 nNumFormat = 0; + double fValue; + if (pFormatter->IsNumberFormat( aFilters[i].MatchValueName, nNumFormat, fValue)) + aFilters[i].MatchValue = ScDPCache::GetLocaleIndependentFormattedString( + fValue, *pFormatter, nNumFormat); + else + aFilters[i].MatchValue = aFilters[i].MatchValueName; + } + + aFilters[i].FieldName = GetString().getString(); + } + + switch (GetStackType()) + { + case svDoubleRef : + PopDoubleRef(aBlock); + break; + case svSingleRef : + { + ScAddress aAddr; + PopSingleRef(aAddr); + aBlock = aAddr; + } + break; + default: + PushError(FormulaError::NoRef); + return; + } + + aDataFieldName = GetString().getString(); // First parameter is data field name. + } + + // Early bail-out, don't grind through data pilot cache and all. + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + + // NOTE : MS Excel docs claim to use the 'most recent' which is not + // exactly the same as what we do in ScDocument::GetDPAtBlock + // However we do need to use GetDPABlock + ScDPObject* pDPObj = mrDoc.GetDPAtBlock(aBlock); + if (!pDPObj) + { + PushError(FormulaError::NoRef); + return; + } + + if (bOldSyntax) + { + OUString aFilterStr = aDataFieldName; + std::vector<sal_Int16> aFilterFuncs; + if (!pDPObj->ParseFilters(aDataFieldName, aFilters, aFilterFuncs, aFilterStr)) + { + PushError(FormulaError::NoRef); + return; + } + + // TODO : For now, we ignore filter functions since we couldn't find a + // live example of how they are supposed to be used. We'll support + // this again once we come across a real-world example. + } + + double fVal = pDPObj->GetPivotData(aDataFieldName, aFilters); + if (std::isnan(fVal)) + { + PushError(FormulaError::NoRef); + return; + } + PushDouble(fVal); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/interpr3.cxx b/sc/source/core/tool/interpr3.cxx new file mode 100644 index 0000000000..88b32b44af --- /dev/null +++ b/sc/source/core/tool/interpr3.cxx @@ -0,0 +1,5585 @@ +/* -*- 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 <tools/solar.h> +#include <stdlib.h> + +#include <interpre.hxx> +#include <global.hxx> +#include <document.hxx> +#include <dociter.hxx> +#include <matrixoperators.hxx> +#include <scmatrix.hxx> + +#include <cassert> +#include <cmath> +#include <memory> +#include <set> +#include <vector> +#include <algorithm> +#include <comphelper/random.hxx> +#include <o3tl/float_int_conversion.hxx> +#include <osl/diagnose.h> + +using ::std::vector; +using namespace formula; + +/// Two columns of data should be sortable with GetSortArray() and QuickSort() +// This is an arbitrary limit. +static size_t MAX_COUNT_DOUBLE_FOR_SORT(const ScSheetLimits& rSheetLimits) +{ + return rSheetLimits.GetMaxRowCount() * 2; +} + +const double ScInterpreter::fMaxGammaArgument = 171.624376956302; // found experimental +const double fMachEps = ::std::numeric_limits<double>::epsilon(); + +namespace { + +class ScDistFunc +{ +public: + virtual double GetValue(double x) const = 0; + +protected: + ~ScDistFunc() {} +}; + +} + +// iteration for inverse distributions + +//template< class T > double lcl_IterateInverse( const T& rFunction, double x0, double x1, bool& rConvError ) + +/** u*w<0.0 fails for values near zero */ +static bool lcl_HasChangeOfSign( double u, double w ) +{ + return (u < 0.0 && w > 0.0) || (u > 0.0 && w < 0.0); +} + +static double lcl_IterateInverse( const ScDistFunc& rFunction, double fAx, double fBx, bool& rConvError ) +{ + rConvError = false; + const double fYEps = 1.0E-307; + const double fXEps = ::std::numeric_limits<double>::epsilon(); + + OSL_ENSURE(fAx<fBx, "IterateInverse: wrong interval"); + + // find enclosing interval + + KahanSum fkAx = fAx; + KahanSum fkBx = fBx; + double fAy = rFunction.GetValue(fAx); + double fBy = rFunction.GetValue(fBx); + KahanSum fTemp; + unsigned short nCount; + for (nCount = 0; nCount < 1000 && !lcl_HasChangeOfSign(fAy,fBy); nCount++) + { + if (std::abs(fAy) <= std::abs(fBy)) + { + fTemp = fkAx; + fkAx += (fkAx - fkBx) * 2.0; + if (fkAx < 0.0) + fkAx = 0.0; + fkBx = fTemp; + fBy = fAy; + fAy = rFunction.GetValue(fkAx.get()); + } + else + { + fTemp = fkBx; + fkBx += (fkBx - fkAx) * 2.0; + fkAx = fTemp; + fAy = fBy; + fBy = rFunction.GetValue(fkBx.get()); + } + } + + fAx = fkAx.get(); + fBx = fkBx.get(); + if (fAy == 0.0) + return fAx; + if (fBy == 0.0) + return fBx; + if (!lcl_HasChangeOfSign( fAy, fBy)) + { + rConvError = true; + return 0.0; + } + // inverse quadric interpolation with additional brackets + // set three points + double fPx = fAx; + double fPy = fAy; + double fQx = fBx; + double fQy = fBy; + double fRx = fAx; + double fRy = fAy; + double fSx = 0.5 * (fAx + fBx); // potential next point + bool bHasToInterpolate = true; + nCount = 0; + while ( nCount < 500 && std::abs(fRy) > fYEps && + (fBx-fAx) > ::std::max( std::abs(fAx), std::abs(fBx)) * fXEps ) + { + if (bHasToInterpolate) + { + if (fPy!=fQy && fQy!=fRy && fRy!=fPy) + { + fSx = fPx * fRy * fQy / (fRy-fPy) / (fQy-fPy) + + fRx * fQy * fPy / (fQy-fRy) / (fPy-fRy) + + fQx * fPy * fRy / (fPy-fQy) / (fRy-fQy); + bHasToInterpolate = (fAx < fSx) && (fSx < fBx); // inside the brackets? + } + else + bHasToInterpolate = false; + } + if(!bHasToInterpolate) + { + fSx = 0.5 * (fAx + fBx); + // reset points + fQx = fBx; fQy = fBy; + bHasToInterpolate = true; + } + // shift points for next interpolation + fPx = fQx; fQx = fRx; fRx = fSx; + fPy = fQy; fQy = fRy; fRy = rFunction.GetValue(fSx); + // update brackets + if (lcl_HasChangeOfSign( fAy, fRy)) + { + fBx = fRx; fBy = fRy; + } + else + { + fAx = fRx; fAy = fRy; + } + // if last iteration brought too small advance, then do bisection next + // time, for safety + bHasToInterpolate = bHasToInterpolate && (std::abs(fRy) * 2.0 <= std::abs(fQy)); + ++nCount; + } + return fRx; +} + +// General functions + +void ScInterpreter::ScNoName() +{ + PushError(FormulaError::NoName); +} + +void ScInterpreter::ScBadName() +{ + short nParamCount = GetByte(); + while (nParamCount-- > 0) + { + PopError(); + } + PushError( FormulaError::NoName); +} + +double ScInterpreter::phi(double x) +{ + return 0.39894228040143268 * exp(-(x * x) / 2.0); +} + +double ScInterpreter::integralPhi(double x) +{ // Using gauss(x)+0.5 has severe cancellation errors for x<-4 + return 0.5 * std::erfc(-x * M_SQRT1_2); +} + +double ScInterpreter::taylor(const double* pPolynom, sal_uInt16 nMax, double x) +{ + KahanSum nVal = pPolynom[nMax]; + for (short i = nMax-1; i >= 0; i--) + { + nVal = (nVal * x) + pPolynom[i]; + } + return nVal.get(); +} + +double ScInterpreter::gauss(double x) +{ + + double xAbs = std::abs(x); + sal_uInt16 xShort = static_cast<sal_uInt16>(::rtl::math::approxFloor(xAbs)); + double nVal = 0.0; + if (xShort == 0) + { + static const double t0[] = + { 0.39894228040143268, -0.06649038006690545, 0.00997355701003582, + -0.00118732821548045, 0.00011543468761616, -0.00000944465625950, + 0.00000066596935163, -0.00000004122667415, 0.00000000227352982, + 0.00000000011301172, 0.00000000000511243, -0.00000000000021218 }; + nVal = taylor(t0, 11, (xAbs * xAbs)) * xAbs; + } + else if (xShort <= 2) + { + static const double t2[] = + { 0.47724986805182079, 0.05399096651318805, -0.05399096651318805, + 0.02699548325659403, -0.00449924720943234, -0.00224962360471617, + 0.00134977416282970, -0.00011783742691370, -0.00011515930357476, + 0.00003704737285544, 0.00000282690796889, -0.00000354513195524, + 0.00000037669563126, 0.00000019202407921, -0.00000005226908590, + -0.00000000491799345, 0.00000000366377919, -0.00000000015981997, + -0.00000000017381238, 0.00000000002624031, 0.00000000000560919, + -0.00000000000172127, -0.00000000000008634, 0.00000000000007894 }; + nVal = taylor(t2, 23, (xAbs - 2.0)); + } + else if (xShort <= 4) + { + static const double t4[] = + { 0.49996832875816688, 0.00013383022576489, -0.00026766045152977, + 0.00033457556441221, -0.00028996548915725, 0.00018178605666397, + -0.00008252863922168, 0.00002551802519049, -0.00000391665839292, + -0.00000074018205222, 0.00000064422023359, -0.00000017370155340, + 0.00000000909595465, 0.00000000944943118, -0.00000000329957075, + 0.00000000029492075, 0.00000000011874477, -0.00000000004420396, + 0.00000000000361422, 0.00000000000143638, -0.00000000000045848 }; + nVal = taylor(t4, 20, (xAbs - 4.0)); + } + else + { + static const double asympt[] = { -1.0, 1.0, -3.0, 15.0, -105.0 }; + nVal = 0.5 + phi(xAbs) * taylor(asympt, 4, 1.0 / (xAbs * xAbs)) / xAbs; + } + if (x < 0.0) + return -nVal; + else + return nVal; +} + +// #i26836# new gaussinv implementation by Martin Eitzenberger <m.eitzenberger@unix.net> + +double ScInterpreter::gaussinv(double x) +{ + double q,t,z; + + q=x-0.5; + + if(std::abs(q)<=.425) + { + t=0.180625-q*q; + + z= + q* + ( + ( + ( + ( + ( + ( + ( + t*2509.0809287301226727+33430.575583588128105 + ) + *t+67265.770927008700853 + ) + *t+45921.953931549871457 + ) + *t+13731.693765509461125 + ) + *t+1971.5909503065514427 + ) + *t+133.14166789178437745 + ) + *t+3.387132872796366608 + ) + / + ( + ( + ( + ( + ( + ( + ( + t*5226.495278852854561+28729.085735721942674 + ) + *t+39307.89580009271061 + ) + *t+21213.794301586595867 + ) + *t+5394.1960214247511077 + ) + *t+687.1870074920579083 + ) + *t+42.313330701600911252 + ) + *t+1.0 + ); + + } + else + { + if(q>0) t=1-x; + else t=x; + + t=sqrt(-log(t)); + + if(t<=5.0) + { + t+=-1.6; + + z= + ( + ( + ( + ( + ( + ( + ( + t*7.7454501427834140764e-4+0.0227238449892691845833 + ) + *t+0.24178072517745061177 + ) + *t+1.27045825245236838258 + ) + *t+3.64784832476320460504 + ) + *t+5.7694972214606914055 + ) + *t+4.6303378461565452959 + ) + *t+1.42343711074968357734 + ) + / + ( + ( + ( + ( + ( + ( + ( + t*1.05075007164441684324e-9+5.475938084995344946e-4 + ) + *t+0.0151986665636164571966 + ) + *t+0.14810397642748007459 + ) + *t+0.68976733498510000455 + ) + *t+1.6763848301838038494 + ) + *t+2.05319162663775882187 + ) + *t+1.0 + ); + + } + else + { + t+=-5.0; + + z= + ( + ( + ( + ( + ( + ( + ( + t*2.01033439929228813265e-7+2.71155556874348757815e-5 + ) + *t+0.0012426609473880784386 + ) + *t+0.026532189526576123093 + ) + *t+0.29656057182850489123 + ) + *t+1.7848265399172913358 + ) + *t+5.4637849111641143699 + ) + *t+6.6579046435011037772 + ) + / + ( + ( + ( + ( + ( + ( + ( + t*2.04426310338993978564e-15+1.4215117583164458887e-7 + ) + *t+1.8463183175100546818e-5 + ) + *t+7.868691311456132591e-4 + ) + *t+0.0148753612908506148525 + ) + *t+0.13692988092273580531 + ) + *t+0.59983220655588793769 + ) + *t+1.0 + ); + + } + + if(q<0.0) z=-z; + } + + return z; +} + +double ScInterpreter::Fakultaet(double x) +{ + x = ::rtl::math::approxFloor(x); + if (x < 0.0) + return 0.0; + else if (x == 0.0) + return 1.0; + else if (x <= 170.0) + { + double fTemp = x; + while (fTemp > 2.0) + { + fTemp--; + x *= fTemp; + } + } + else + SetError(FormulaError::NoValue); + return x; +} + +double ScInterpreter::BinomKoeff(double n, double k) +{ + // this method has been duplicated as BinomialCoefficient() + // in scaddins/source/analysis/analysishelper.cxx + + double nVal = 0.0; + k = ::rtl::math::approxFloor(k); + if (n < k) + nVal = 0.0; + else if (k == 0.0) + nVal = 1.0; + else + { + nVal = n/k; + n--; + k--; + while (k > 0.0) + { + nVal *= n/k; + k--; + n--; + } + + } + return nVal; +} + +// The algorithm is based on lanczos13m53 in lanczos.hpp +// in math library from http://www.boost.org +/** you must ensure fZ>0 + Uses a variant of the Lanczos sum with a rational function. */ +static double lcl_getLanczosSum(double fZ) +{ + static const double fNum[13] ={ + 23531376880.41075968857200767445163675473, + 42919803642.64909876895789904700198885093, + 35711959237.35566804944018545154716670596, + 17921034426.03720969991975575445893111267, + 6039542586.35202800506429164430729792107, + 1439720407.311721673663223072794912393972, + 248874557.8620541565114603864132294232163, + 31426415.58540019438061423162831820536287, + 2876370.628935372441225409051620849613599, + 186056.2653952234950402949897160456992822, + 8071.672002365816210638002902272250613822, + 210.8242777515793458725097339207133627117, + 2.506628274631000270164908177133837338626 + }; + static const double fDenom[13] = { + 0, + 39916800, + 120543840, + 150917976, + 105258076, + 45995730, + 13339535, + 2637558, + 357423, + 32670, + 1925, + 66, + 1 + }; + // Horner scheme + double fSumNum; + double fSumDenom; + int nI; + if (fZ<=1.0) + { + fSumNum = fNum[12]; + fSumDenom = fDenom[12]; + for (nI = 11; nI >= 0; --nI) + { + fSumNum *= fZ; + fSumNum += fNum[nI]; + fSumDenom *= fZ; + fSumDenom += fDenom[nI]; + } + } + else + // Cancel down with fZ^12; Horner scheme with reverse coefficients + { + double fZInv = 1/fZ; + fSumNum = fNum[0]; + fSumDenom = fDenom[0]; + for (nI = 1; nI <=12; ++nI) + { + fSumNum *= fZInv; + fSumNum += fNum[nI]; + fSumDenom *= fZInv; + fSumDenom += fDenom[nI]; + } + } + return fSumNum/fSumDenom; +} + +// The algorithm is based on tgamma in gamma.hpp +// in math library from http://www.boost.org +/** You must ensure fZ>0; fZ>171.624376956302 will overflow. */ +static double lcl_GetGammaHelper(double fZ) +{ + double fGamma = lcl_getLanczosSum(fZ); + const double fg = 6.024680040776729583740234375; + double fZgHelp = fZ + fg - 0.5; + // avoid intermediate overflow + double fHalfpower = pow( fZgHelp, fZ / 2 - 0.25); + fGamma *= fHalfpower; + fGamma /= exp(fZgHelp); + fGamma *= fHalfpower; + if (fZ <= 20.0 && fZ == ::rtl::math::approxFloor(fZ)) + fGamma = ::rtl::math::round(fGamma); + return fGamma; +} + +// The algorithm is based on tgamma in gamma.hpp +// in math library from http://www.boost.org +/** You must ensure fZ>0 */ +static double lcl_GetLogGammaHelper(double fZ) +{ + const double fg = 6.024680040776729583740234375; + double fZgHelp = fZ + fg - 0.5; + return log( lcl_getLanczosSum(fZ)) + (fZ-0.5) * log(fZgHelp) - fZgHelp; +} + +/** You must ensure non integer arguments for fZ<1 */ +double ScInterpreter::GetGamma(double fZ) +{ + const double fLogPi = log(M_PI); + const double fLogDblMax = log( ::std::numeric_limits<double>::max()); + + if (fZ > fMaxGammaArgument) + { + SetError(FormulaError::IllegalFPOperation); + return HUGE_VAL; + } + + if (fZ >= 1.0) + return lcl_GetGammaHelper(fZ); + + if (fZ >= 0.5) // shift to x>=1 using Gamma(x)=Gamma(x+1)/x + return lcl_GetGammaHelper(fZ+1) / fZ; + + if (fZ >= -0.5) // shift to x>=1, might overflow + { + double fLogTest = lcl_GetLogGammaHelper(fZ+2) - std::log1p(fZ) - log( std::abs(fZ)); + if (fLogTest >= fLogDblMax) + { + SetError( FormulaError::IllegalFPOperation); + return HUGE_VAL; + } + return lcl_GetGammaHelper(fZ+2) / (fZ+1) / fZ; + } + // fZ<-0.5 + // Use Euler's reflection formula: gamma(x)= pi/ ( gamma(1-x)*sin(pi*x) ) + double fLogDivisor = lcl_GetLogGammaHelper(1-fZ) + log( std::abs( ::rtl::math::sin( M_PI*fZ))); + if (fLogDivisor - fLogPi >= fLogDblMax) // underflow + return 0.0; + + if (fLogDivisor<0.0) + if (fLogPi - fLogDivisor > fLogDblMax) // overflow + { + SetError(FormulaError::IllegalFPOperation); + return HUGE_VAL; + } + + return exp( fLogPi - fLogDivisor) * ((::rtl::math::sin( M_PI*fZ) < 0.0) ? -1.0 : 1.0); +} + +/** You must ensure fZ>0 */ +double ScInterpreter::GetLogGamma(double fZ) +{ + if (fZ >= fMaxGammaArgument) + return lcl_GetLogGammaHelper(fZ); + if (fZ >= 1.0) + return log(lcl_GetGammaHelper(fZ)); + if (fZ >= 0.5) + return log( lcl_GetGammaHelper(fZ+1) / fZ); + return lcl_GetLogGammaHelper(fZ+2) - std::log1p(fZ) - log(fZ); +} + +double ScInterpreter::GetFDist(double x, double fF1, double fF2) +{ + double arg = fF2/(fF2+fF1*x); + double alpha = fF2/2.0; + double beta = fF1/2.0; + return GetBetaDist(arg, alpha, beta); +} + +double ScInterpreter::GetTDist( double T, double fDF, int nType ) +{ + switch ( nType ) + { + case 1 : // 1-tailed T-distribution + return 0.5 * GetBetaDist( fDF / ( fDF + T * T ), fDF / 2.0, 0.5 ); + case 2 : // 2-tailed T-distribution + return GetBetaDist( fDF / ( fDF + T * T ), fDF / 2.0, 0.5); + case 3 : // left-tailed T-distribution (probability density function) + return pow( 1 + ( T * T / fDF ), -( fDF + 1 ) / 2 ) / ( sqrt( fDF ) * GetBeta( 0.5, fDF / 2.0 ) ); + case 4 : // left-tailed T-distribution (cumulative distribution function) + double X = fDF / ( T * T + fDF ); + double R = 0.5 * GetBetaDist( X, 0.5 * fDF, 0.5 ); + return ( T < 0 ? R : 1 - R ); + } + SetError( FormulaError::IllegalArgument ); + return HUGE_VAL; +} + +// for LEGACY.CHIDIST, returns right tail, fDF=degrees of freedom +/** You must ensure fDF>0.0 */ +double ScInterpreter::GetChiDist(double fX, double fDF) +{ + if (fX <= 0.0) + return 1.0; // see ODFF + else + return GetUpRegIGamma( fDF/2.0, fX/2.0); +} + +// ready for ODF 1.2 +// for ODF CHISQDIST; cumulative distribution function, fDF=degrees of freedom +// returns left tail +/** You must ensure fDF>0.0 */ +double ScInterpreter::GetChiSqDistCDF(double fX, double fDF) +{ + if (fX <= 0.0) + return 0.0; // see ODFF + else + return GetLowRegIGamma( fDF/2.0, fX/2.0); +} + +double ScInterpreter::GetChiSqDistPDF(double fX, double fDF) +{ + // you must ensure fDF is positive integer + double fValue; + if (fX <= 0.0) + return 0.0; // see ODFF + if (fDF*fX > 1391000.0) + { + // intermediate invalid values, use log + fValue = exp((0.5*fDF - 1) * log(fX*0.5) - 0.5 * fX - log(2.0) - GetLogGamma(0.5*fDF)); + } + else // fDF is small in most cases, we can iterate + { + double fCount; + if (fmod(fDF,2.0)<0.5) + { + // even + fValue = 0.5; + fCount = 2.0; + } + else + { + fValue = 1/sqrt(fX*2*M_PI); + fCount = 1.0; + } + while ( fCount < fDF) + { + fValue *= (fX / fCount); + fCount += 2.0; + } + if (fX>=1425.0) // underflow in e^(-x/2) + fValue = exp(log(fValue)-fX/2); + else + fValue *= exp(-fX/2); + } + return fValue; +} + +void ScInterpreter::ScChiSqDist() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2, 3 ) ) + return; + bool bCumulative; + if (nParamCount == 3) + bCumulative = GetBool(); + else + bCumulative = true; + double fDF = ::rtl::math::approxFloor(GetDouble()); + if (fDF < 1.0) + PushIllegalArgument(); + else + { + double fX = GetDouble(); + if (bCumulative) + PushDouble(GetChiSqDistCDF(fX,fDF)); + else + PushDouble(GetChiSqDistPDF(fX,fDF)); + } +} + +void ScInterpreter::ScChiSqDist_MS() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 3, 3 ) ) + return; + bool bCumulative = GetBool(); + double fDF = ::rtl::math::approxFloor( GetDouble() ); + if ( fDF < 1.0 || fDF > 1E10 ) + PushIllegalArgument(); + else + { + double fX = GetDouble(); + if ( fX < 0 ) + PushIllegalArgument(); + else + { + if ( bCumulative ) + PushDouble( GetChiSqDistCDF( fX, fDF ) ); + else + PushDouble( GetChiSqDistPDF( fX, fDF ) ); + } + } +} + +void ScInterpreter::ScGamma() +{ + double x = GetDouble(); + if (x <= 0.0 && x == ::rtl::math::approxFloor(x)) + PushIllegalArgument(); + else + { + double fResult = GetGamma(x); + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + PushDouble(fResult); + } +} + +void ScInterpreter::ScLogGamma() +{ + double x = GetDouble(); + if (x > 0.0) // constraint from ODFF + PushDouble( GetLogGamma(x)); + else + PushIllegalArgument(); +} + +double ScInterpreter::GetBeta(double fAlpha, double fBeta) +{ + double fA; + double fB; + if (fAlpha > fBeta) + { + fA = fAlpha; fB = fBeta; + } + else + { + fA = fBeta; fB = fAlpha; + } + if (fA+fB < fMaxGammaArgument) // simple case + return GetGamma(fA)/GetGamma(fA+fB)*GetGamma(fB); + // need logarithm + // GetLogGamma is not accurate enough, back to Lanczos for all three + // GetGamma and arrange factors newly. + const double fg = 6.024680040776729583740234375; //see GetGamma + double fgm = fg - 0.5; + double fLanczos = lcl_getLanczosSum(fA); + fLanczos /= lcl_getLanczosSum(fA+fB); + fLanczos *= lcl_getLanczosSum(fB); + double fABgm = fA+fB+fgm; + fLanczos *= sqrt((fABgm/(fA+fgm))/(fB+fgm)); + double fTempA = fB/(fA+fgm); // (fA+fgm)/fABgm = 1 / ( 1 + fB/(fA+fgm)) + double fTempB = fA/(fB+fgm); + double fResult = exp(-fA * std::log1p(fTempA) + -fB * std::log1p(fTempB)-fgm); + fResult *= fLanczos; + return fResult; +} + +// Same as GetBeta but with logarithm +double ScInterpreter::GetLogBeta(double fAlpha, double fBeta) +{ + double fA; + double fB; + if (fAlpha > fBeta) + { + fA = fAlpha; fB = fBeta; + } + else + { + fA = fBeta; fB = fAlpha; + } + const double fg = 6.024680040776729583740234375; //see GetGamma + double fgm = fg - 0.5; + double fLanczos = lcl_getLanczosSum(fA); + fLanczos /= lcl_getLanczosSum(fA+fB); + fLanczos *= lcl_getLanczosSum(fB); + double fLogLanczos = log(fLanczos); + double fABgm = fA+fB+fgm; + fLogLanczos += 0.5*(log(fABgm)-log(fA+fgm)-log(fB+fgm)); + double fTempA = fB/(fA+fgm); // (fA+fgm)/fABgm = 1 / ( 1 + fB/(fA+fgm)) + double fTempB = fA/(fB+fgm); + double fResult = -fA * std::log1p(fTempA) + -fB * std::log1p(fTempB)-fgm; + fResult += fLogLanczos; + return fResult; +} + +// beta distribution probability density function +double ScInterpreter::GetBetaDistPDF(double fX, double fA, double fB) +{ + // special cases + if (fA == 1.0) // result b*(1-x)^(b-1) + { + if (fB == 1.0) + return 1.0; + if (fB == 2.0) + return -2.0*fX + 2.0; + if (fX == 1.0 && fB < 1.0) + { + SetError(FormulaError::IllegalArgument); + return HUGE_VAL; + } + if (fX <= 0.01) + return fB + fB * std::expm1((fB-1.0) * std::log1p(-fX)); + else + return fB * pow(0.5-fX+0.5,fB-1.0); + } + if (fB == 1.0) // result a*x^(a-1) + { + if (fA == 2.0) + return fA * fX; + if (fX == 0.0 && fA < 1.0) + { + SetError(FormulaError::IllegalArgument); + return HUGE_VAL; + } + return fA * pow(fX,fA-1); + } + if (fX <= 0.0) + { + if (fA < 1.0 && fX == 0.0) + { + SetError(FormulaError::IllegalArgument); + return HUGE_VAL; + } + else + return 0.0; + } + if (fX >= 1.0) + { + if (fB < 1.0 && fX == 1.0) + { + SetError(FormulaError::IllegalArgument); + return HUGE_VAL; + } + else + return 0.0; + } + + // normal cases; result x^(a-1)*(1-x)^(b-1)/Beta(a,b) + const double fLogDblMax = log( ::std::numeric_limits<double>::max()); + const double fLogDblMin = log( ::std::numeric_limits<double>::min()); + double fLogY = (fX < 0.1) ? std::log1p(-fX) : log(0.5-fX+0.5); + double fLogX = log(fX); + double fAm1LogX = (fA-1.0) * fLogX; + double fBm1LogY = (fB-1.0) * fLogY; + double fLogBeta = GetLogBeta(fA,fB); + // check whether parts over- or underflow + if ( fAm1LogX < fLogDblMax && fAm1LogX > fLogDblMin + && fBm1LogY < fLogDblMax && fBm1LogY > fLogDblMin + && fLogBeta < fLogDblMax && fLogBeta > fLogDblMin + && fAm1LogX + fBm1LogY < fLogDblMax && fAm1LogX + fBm1LogY > fLogDblMin) + return pow(fX,fA-1.0) * pow(0.5-fX+0.5,fB-1.0) / GetBeta(fA,fB); + else // need logarithm; + // might overflow as a whole, but seldom, not worth to pre-detect it + return exp( fAm1LogX + fBm1LogY - fLogBeta); +} + +/* + x^a * (1-x)^b + I_x(a,b) = ---------------- * result of ContFrac + a * Beta(a,b) +*/ +static double lcl_GetBetaHelperContFrac(double fX, double fA, double fB) +{ // like old version + double a1, b1, a2, b2, fnorm, cfnew, cf; + a1 = 1.0; b1 = 1.0; + b2 = 1.0 - (fA+fB)/(fA+1.0)*fX; + if (b2 == 0.0) + { + a2 = 0.0; + fnorm = 1.0; + cf = 1.0; + } + else + { + a2 = 1.0; + fnorm = 1.0/b2; + cf = a2*fnorm; + } + cfnew = 1.0; + double rm = 1.0; + + const double fMaxIter = 50000.0; + // loop security, normal cases converge in less than 100 iterations. + // FIXME: You will get so much iterations for fX near mean, + // I do not know a better algorithm. + bool bfinished = false; + do + { + const double apl2m = fA + 2.0*rm; + const double d2m = rm*(fB-rm)*fX/((apl2m-1.0)*apl2m); + const double d2m1 = -(fA+rm)*(fA+fB+rm)*fX/(apl2m*(apl2m+1.0)); + a1 = (a2+d2m*a1)*fnorm; + b1 = (b2+d2m*b1)*fnorm; + a2 = a1 + d2m1*a2*fnorm; + b2 = b1 + d2m1*b2*fnorm; + if (b2 != 0.0) + { + fnorm = 1.0/b2; + cfnew = a2*fnorm; + bfinished = (std::abs(cf-cfnew) < std::abs(cf)*fMachEps); + } + cf = cfnew; + rm += 1.0; + } + while (rm < fMaxIter && !bfinished); + return cf; +} + +// cumulative distribution function, normalized +double ScInterpreter::GetBetaDist(double fXin, double fAlpha, double fBeta) +{ +// special cases + if (fXin <= 0.0) // values are valid, see spec + return 0.0; + if (fXin >= 1.0) // values are valid, see spec + return 1.0; + if (fBeta == 1.0) + return pow(fXin, fAlpha); + if (fAlpha == 1.0) + // 1.0 - pow(1.0-fX,fBeta) is not accurate enough + return -std::expm1(fBeta * std::log1p(-fXin)); + //FIXME: need special algorithm for fX near fP for large fA,fB + double fResult; + // I use always continued fraction, power series are neither + // faster nor more accurate. + double fY = (0.5-fXin)+0.5; + double flnY = std::log1p(-fXin); + double fX = fXin; + double flnX = log(fXin); + double fA = fAlpha; + double fB = fBeta; + bool bReflect = fXin > fAlpha/(fAlpha+fBeta); + if (bReflect) + { + fA = fBeta; + fB = fAlpha; + fX = fY; + fY = fXin; + flnX = flnY; + flnY = log(fXin); + } + fResult = lcl_GetBetaHelperContFrac(fX,fA,fB); + fResult = fResult/fA; + double fP = fA/(fA+fB); + double fQ = fB/(fA+fB); + double fTemp; + if (fA > 1.0 && fB > 1.0 && fP < 0.97 && fQ < 0.97) //found experimental + fTemp = GetBetaDistPDF(fX,fA,fB)*fX*fY; + else + fTemp = exp(fA*flnX + fB*flnY - GetLogBeta(fA,fB)); + fResult *= fTemp; + if (bReflect) + fResult = 0.5 - fResult + 0.5; + if (fResult > 1.0) // ensure valid range + fResult = 1.0; + if (fResult < 0.0) + fResult = 0.0; + return fResult; +} + +void ScInterpreter::ScBetaDist() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 3, 6 ) ) // expanded, see #i91547# + return; + double fLowerBound, fUpperBound; + double alpha, beta, x; + bool bIsCumulative; + if (nParamCount == 6) + bIsCumulative = GetBool(); + else + bIsCumulative = true; + if (nParamCount >= 5) + fUpperBound = GetDouble(); + else + fUpperBound = 1.0; + if (nParamCount >= 4) + fLowerBound = GetDouble(); + else + fLowerBound = 0.0; + beta = GetDouble(); + alpha = GetDouble(); + x = GetDouble(); + double fScale = fUpperBound - fLowerBound; + if (fScale <= 0.0 || alpha <= 0.0 || beta <= 0.0) + { + PushIllegalArgument(); + return; + } + if (bIsCumulative) // cumulative distribution function + { + // special cases + if (x < fLowerBound) + { + PushDouble(0.0); return; //see spec + } + if (x > fUpperBound) + { + PushDouble(1.0); return; //see spec + } + // normal cases + x = (x-fLowerBound)/fScale; // convert to standard form + PushDouble(GetBetaDist(x, alpha, beta)); + return; + } + else // probability density function + { + if (x < fLowerBound || x > fUpperBound) + { + PushDouble(0.0); + return; + } + x = (x-fLowerBound)/fScale; + PushDouble(GetBetaDistPDF(x, alpha, beta)/fScale); + return; + } +} + +/** + Microsoft version has parameters in different order + Also, upper and lowerbound are optional and have default values + and different constraints apply. + Basically, function is identical with ScInterpreter::ScBetaDist() +*/ +void ScInterpreter::ScBetaDist_MS() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 4, 6 ) ) + return; + double fLowerBound, fUpperBound; + double alpha, beta, x; + bool bIsCumulative; + if (nParamCount == 6) + fUpperBound = GetDouble(); + else + fUpperBound = 1.0; + if (nParamCount >= 5) + fLowerBound = GetDouble(); + else + fLowerBound = 0.0; + bIsCumulative = GetBool(); + beta = GetDouble(); + alpha = GetDouble(); + x = GetDouble(); + if (alpha <= 0.0 || beta <= 0.0 || x < fLowerBound || x > fUpperBound) + { + PushIllegalArgument(); + return; + } + double fScale = fUpperBound - fLowerBound; + if (bIsCumulative) // cumulative distribution function + { + x = (x-fLowerBound)/fScale; // convert to standard form + PushDouble(GetBetaDist(x, alpha, beta)); + return; + } + else // probability density function + { + x = (x-fLowerBound)/fScale; + PushDouble(GetBetaDistPDF(x, alpha, beta)/fScale); + return; + } +} + +void ScInterpreter::ScPhi() +{ + PushDouble(phi(GetDouble())); +} + +void ScInterpreter::ScGauss() +{ + PushDouble(gauss(GetDouble())); +} + +void ScInterpreter::ScFisher() +{ + double fVal = GetDouble(); + if (std::abs(fVal) >= 1.0) + PushIllegalArgument(); + else + PushDouble(::atanh(fVal)); +} + +void ScInterpreter::ScFisherInv() +{ + PushDouble( tanh( GetDouble())); +} + +void ScInterpreter::ScFact() +{ + double nVal = GetDouble(); + if (nVal < 0.0) + PushIllegalArgument(); + else + PushDouble(Fakultaet(nVal)); +} + +void ScInterpreter::ScCombin() +{ + if ( MustHaveParamCount( GetByte(), 2 ) ) + { + double k = ::rtl::math::approxFloor(GetDouble()); + double n = ::rtl::math::approxFloor(GetDouble()); + if (k < 0.0 || n < 0.0 || k > n) + PushIllegalArgument(); + else + PushDouble(BinomKoeff(n, k)); + } +} + +void ScInterpreter::ScCombinA() +{ + if ( MustHaveParamCount( GetByte(), 2 ) ) + { + double k = ::rtl::math::approxFloor(GetDouble()); + double n = ::rtl::math::approxFloor(GetDouble()); + if (k < 0.0 || n < 0.0 || k > n) + PushIllegalArgument(); + else + PushDouble(BinomKoeff(n + k - 1, k)); + } +} + +void ScInterpreter::ScPermut() +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + double k = ::rtl::math::approxFloor(GetDouble()); + double n = ::rtl::math::approxFloor(GetDouble()); + if (n < 0.0 || k < 0.0 || k > n) + PushIllegalArgument(); + else if (k == 0.0) + PushInt(1); // (n! / (n - 0)!) == 1 + else + { + double nVal = n; + for (sal_uLong i = static_cast<sal_uLong>(k)-1; i >= 1; i--) + nVal *= n-static_cast<double>(i); + PushDouble(nVal); + } +} + +void ScInterpreter::ScPermutationA() +{ + if ( MustHaveParamCount( GetByte(), 2 ) ) + { + double k = ::rtl::math::approxFloor(GetDouble()); + double n = ::rtl::math::approxFloor(GetDouble()); + if (n < 0.0 || k < 0.0) + PushIllegalArgument(); + else + PushDouble(pow(n,k)); + } +} + +double ScInterpreter::GetBinomDistPMF(double x, double n, double p) +// used in ScB and ScBinomDist +// preconditions: 0.0 <= x <= n, 0.0 < p < 1.0; x,n integral although double +{ + double q = (0.5 - p) + 0.5; + double fFactor = pow(q, n); + if (fFactor <=::std::numeric_limits<double>::min()) + { + fFactor = pow(p, n); + if (fFactor <= ::std::numeric_limits<double>::min()) + return GetBetaDistPDF(p, x+1.0, n-x+1.0)/(n+1.0); + else + { + sal_uInt32 max = static_cast<sal_uInt32>(n - x); + for (sal_uInt32 i = 0; i < max && fFactor > 0.0; i++) + fFactor *= (n-i)/(i+1)*q/p; + return fFactor; + } + } + else + { + sal_uInt32 max = static_cast<sal_uInt32>(x); + for (sal_uInt32 i = 0; i < max && fFactor > 0.0; i++) + fFactor *= (n-i)/(i+1)*p/q; + return fFactor; + } +} + +static double lcl_GetBinomDistRange(double n, double xs,double xe, + double fFactor /* q^n */, double p, double q) +//preconditions: 0.0 <= xs < xe <= n; xs,xe,n integral although double +{ + sal_uInt32 i; + // skip summands index 0 to xs-1, start sum with index xs + sal_uInt32 nXs = static_cast<sal_uInt32>( xs ); + for (i = 1; i <= nXs && fFactor > 0.0; i++) + fFactor *= (n-i+1)/i * p/q; + KahanSum fSum = fFactor; // Summand xs + sal_uInt32 nXe = static_cast<sal_uInt32>(xe); + for (i = nXs+1; i <= nXe && fFactor > 0.0; i++) + { + fFactor *= (n-i+1)/i * p/q; + fSum += fFactor; + } + return std::min(fSum.get(), 1.0); +} + +void ScInterpreter::ScB() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 3, 4 ) ) + return ; + if (nParamCount == 3) // mass function + { + double x = ::rtl::math::approxFloor(GetDouble()); + double p = GetDouble(); + double n = ::rtl::math::approxFloor(GetDouble()); + if (n < 0.0 || x < 0.0 || x > n || p < 0.0 || p > 1.0) + PushIllegalArgument(); + else if (p == 0.0) + PushDouble( (x == 0.0) ? 1.0 : 0.0 ); + else if ( p == 1.0) + PushDouble( (x == n) ? 1.0 : 0.0); + else + PushDouble(GetBinomDistPMF(x,n,p)); + } + else + { // nParamCount == 4 + double xe = ::rtl::math::approxFloor(GetDouble()); + double xs = ::rtl::math::approxFloor(GetDouble()); + double p = GetDouble(); + double n = ::rtl::math::approxFloor(GetDouble()); + double q = (0.5 - p) + 0.5; + bool bIsValidX = ( 0.0 <= xs && xs <= xe && xe <= n); + if ( bIsValidX && 0.0 < p && p < 1.0) + { + if (xs == xe) // mass function + PushDouble(GetBinomDistPMF(xs,n,p)); + else + { + double fFactor = pow(q, n); + if (fFactor > ::std::numeric_limits<double>::min()) + PushDouble(lcl_GetBinomDistRange(n,xs,xe,fFactor,p,q)); + else + { + fFactor = pow(p, n); + if (fFactor > ::std::numeric_limits<double>::min()) + { + // sum from j=xs to xe {(n choose j) * p^j * q^(n-j)} + // = sum from i = n-xe to n-xs { (n choose i) * q^i * p^(n-i)} + PushDouble(lcl_GetBinomDistRange(n,n-xe,n-xs,fFactor,q,p)); + } + else + PushDouble(GetBetaDist(q,n-xe,xe+1.0)-GetBetaDist(q,n-xs+1,xs) ); + } + } + } + else + { + if ( bIsValidX ) // not(0<p<1) + { + if ( p == 0.0 ) + PushDouble( (xs == 0.0) ? 1.0 : 0.0 ); + else if ( p == 1.0 ) + PushDouble( (xe == n) ? 1.0 : 0.0 ); + else + PushIllegalArgument(); + } + else + PushIllegalArgument(); + } + } +} + +void ScInterpreter::ScBinomDist() +{ + if ( !MustHaveParamCount( GetByte(), 4 ) ) + return; + + bool bIsCum = GetBool(); // false=mass function; true=cumulative + double p = GetDouble(); + double n = ::rtl::math::approxFloor(GetDouble()); + double x = ::rtl::math::approxFloor(GetDouble()); + double q = (0.5 - p) + 0.5; // get one bit more for p near 1.0 + if (n < 0.0 || x < 0.0 || x > n || p < 0.0 || p > 1.0) + { + PushIllegalArgument(); + return; + } + if ( p == 0.0) + { + PushDouble( (x==0.0 || bIsCum) ? 1.0 : 0.0 ); + return; + } + if ( p == 1.0) + { + PushDouble( (x==n) ? 1.0 : 0.0); + return; + } + if (!bIsCum) + PushDouble( GetBinomDistPMF(x,n,p)); + else + { + if (x == n) + PushDouble(1.0); + else + { + double fFactor = pow(q, n); + if (x == 0.0) + PushDouble(fFactor); + else if (fFactor <= ::std::numeric_limits<double>::min()) + { + fFactor = pow(p, n); + if (fFactor <= ::std::numeric_limits<double>::min()) + PushDouble(GetBetaDist(q,n-x,x+1.0)); + else + { + if (fFactor > fMachEps) + { + double fSum = 1.0 - fFactor; + sal_uInt32 max = static_cast<sal_uInt32> (n - x) - 1; + for (sal_uInt32 i = 0; i < max && fFactor > 0.0; i++) + { + fFactor *= (n-i)/(i+1)*q/p; + fSum -= fFactor; + } + PushDouble( (fSum < 0.0) ? 0.0 : fSum ); + } + else + PushDouble(lcl_GetBinomDistRange(n,n-x,n,fFactor,q,p)); + } + } + else + PushDouble( lcl_GetBinomDistRange(n,0.0,x,fFactor,p,q)) ; + } + } +} + +void ScInterpreter::ScCritBinom() +{ + if ( !MustHaveParamCount( GetByte(), 3 ) ) + return; + + double alpha = GetDouble(); + double p = GetDouble(); + double n = ::rtl::math::approxFloor(GetDouble()); + if (n < 0.0 || alpha < 0.0 || alpha > 1.0 || p < 0.0 || p > 1.0) + PushIllegalArgument(); + else if ( alpha == 0.0 ) + PushDouble( 0.0 ); + else if ( alpha == 1.0 ) + PushDouble( p == 0 ? 0.0 : n ); + else + { + double fFactor; + double q = (0.5 - p) + 0.5; // get one bit more for p near 1.0 + if ( q > p ) // work from the side where the cumulative curve is + { + // work from 0 upwards + fFactor = pow(q,n); + if (fFactor > ::std::numeric_limits<double>::min()) + { + KahanSum fSum = fFactor; + sal_uInt32 max = static_cast<sal_uInt32> (n), i; + for (i = 0; i < max && fSum < alpha; i++) + { + fFactor *= (n-i)/(i+1)*p/q; + fSum += fFactor; + } + PushDouble(i); + } + else + { + // accumulate BinomDist until accumulated BinomDist reaches alpha + KahanSum fSum = 0.0; + sal_uInt32 max = static_cast<sal_uInt32> (n), i; + for (i = 0; i < max && fSum < alpha; i++) + { + const double x = GetBetaDistPDF( p, ( i + 1 ), ( n - i + 1 ) )/( n + 1 ); + if ( nGlobalError == FormulaError::NONE ) + fSum += x; + else + { + PushNoValue(); + return; + } + } + PushDouble( i - 1 ); + } + } + else + { + // work from n backwards + fFactor = pow(p, n); + if (fFactor > ::std::numeric_limits<double>::min()) + { + KahanSum fSum = 1.0 - fFactor; + sal_uInt32 max = static_cast<sal_uInt32> (n), i; + for (i = 0; i < max && fSum >= alpha; i++) + { + fFactor *= (n-i)/(i+1)*q/p; + fSum -= fFactor; + } + PushDouble(n-i); + } + else + { + // accumulate BinomDist until accumulated BinomDist reaches alpha + KahanSum fSum = 0.0; + sal_uInt32 max = static_cast<sal_uInt32> (n), i; + alpha = 1 - alpha; + for (i = 0; i < max && fSum < alpha; i++) + { + const double x = GetBetaDistPDF( q, ( i + 1 ), ( n - i + 1 ) )/( n + 1 ); + if ( nGlobalError == FormulaError::NONE ) + fSum += x; + else + { + PushNoValue(); + return; + } + } + PushDouble( n - i + 1 ); + } + } + } +} + +void ScInterpreter::ScNegBinomDist() +{ + if ( !MustHaveParamCount( GetByte(), 3 ) ) + return; + + double p = GetDouble(); // probability + double s = ::rtl::math::approxFloor(GetDouble()); // No of successes + double f = ::rtl::math::approxFloor(GetDouble()); // No of failures + if ((f + s) <= 1.0 || p < 0.0 || p > 1.0) + PushIllegalArgument(); + else + { + double q = 1.0 - p; + double fFactor = pow(p,s); + for (double i = 0.0; i < f; i++) + fFactor *= (i+s)/(i+1.0)*q; + PushDouble(fFactor); + } +} + +void ScInterpreter::ScNegBinomDist_MS() +{ + if ( !MustHaveParamCount( GetByte(), 4 ) ) + return; + + bool bCumulative = GetBool(); + double p = GetDouble(); // probability + double s = ::rtl::math::approxFloor(GetDouble()); // No of successes + double f = ::rtl::math::approxFloor(GetDouble()); // No of failures + if ( s < 1.0 || f < 0.0 || p < 0.0 || p > 1.0 ) + PushIllegalArgument(); + else + { + double q = 1.0 - p; + if ( bCumulative ) + PushDouble( 1.0 - GetBetaDist( q, f + 1, s ) ); + else + { + double fFactor = pow( p, s ); + for ( double i = 0.0; i < f; i++ ) + fFactor *= ( i + s ) / ( i + 1.0 ) * q; + PushDouble( fFactor ); + } + } +} + +void ScInterpreter::ScNormDist( int nMinParamCount ) +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, nMinParamCount, 4 ) ) + return; + bool bCumulative = nParamCount != 4 || GetBool(); + double sigma = GetDouble(); // standard deviation + double mue = GetDouble(); // mean + double x = GetDouble(); // x + if (sigma <= 0.0) + { + PushIllegalArgument(); + return; + } + if (bCumulative) + PushDouble(integralPhi((x-mue)/sigma)); + else + PushDouble(phi((x-mue)/sigma)/sigma); +} + +void ScInterpreter::ScLogNormDist( int nMinParamCount ) //expanded, see #i100119# and fdo72158 +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, nMinParamCount, 4 ) ) + return; + bool bCumulative = nParamCount != 4 || GetBool(); // cumulative + double sigma = nParamCount >= 3 ? GetDouble() : 1.0; // standard deviation + double mue = nParamCount >= 2 ? GetDouble() : 0.0; // mean + double x = GetDouble(); // x + if (sigma <= 0.0) + { + PushIllegalArgument(); + return; + } + if (bCumulative) + { // cumulative + if (x <= 0.0) + PushDouble(0.0); + else + PushDouble(integralPhi((log(x)-mue)/sigma)); + } + else + { // density + if (x <= 0.0) + PushIllegalArgument(); + else + PushDouble(phi((log(x)-mue)/sigma)/sigma/x); + } +} + +void ScInterpreter::ScStdNormDist() +{ + PushDouble(integralPhi(GetDouble())); +} + +void ScInterpreter::ScStdNormDist_MS() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2 ) ) + return; + bool bCumulative = GetBool(); // cumulative + double x = GetDouble(); // x + + if ( bCumulative ) + PushDouble( integralPhi( x ) ); + else + PushDouble( exp( - pow( x, 2 ) / 2 ) / sqrt( 2 * M_PI ) ); +} + +void ScInterpreter::ScExpDist() +{ + if ( !MustHaveParamCount( GetByte(), 3 ) ) + return; + + double kum = GetDouble(); // 0 or 1 + double lambda = GetDouble(); // lambda + double x = GetDouble(); // x + if (lambda <= 0.0) + PushIllegalArgument(); + else if (kum == 0.0) // density + { + if (x >= 0.0) + PushDouble(lambda * exp(-lambda*x)); + else + PushInt(0); + } + else // distribution + { + if (x > 0.0) + PushDouble(1.0 - exp(-lambda*x)); + else + PushInt(0); + } +} + +void ScInterpreter::ScTDist() +{ + if ( !MustHaveParamCount( GetByte(), 3 ) ) + return; + double fFlag = ::rtl::math::approxFloor(GetDouble()); + double fDF = ::rtl::math::approxFloor(GetDouble()); + double T = GetDouble(); + if (fDF < 1.0 || T < 0.0 || (fFlag != 1.0 && fFlag != 2.0) ) + { + PushIllegalArgument(); + return; + } + PushDouble( GetTDist( T, fDF, static_cast<int>(fFlag) ) ); +} + +void ScInterpreter::ScTDist_T( int nTails ) +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + double fDF = ::rtl::math::approxFloor( GetDouble() ); + double fT = GetDouble(); + if ( fDF < 1.0 || ( nTails == 2 && fT < 0.0 ) ) + { + PushIllegalArgument(); + return; + } + double fRes = GetTDist( fT, fDF, nTails ); + if ( nTails == 1 && fT < 0.0 ) + PushDouble( 1.0 - fRes ); // tdf#105937, right tail, negative X + else + PushDouble( fRes ); +} + +void ScInterpreter::ScTDist_MS() +{ + if ( !MustHaveParamCount( GetByte(), 3 ) ) + return; + bool bCumulative = GetBool(); + double fDF = ::rtl::math::approxFloor( GetDouble() ); + double T = GetDouble(); + if ( fDF < 1.0 ) + { + PushIllegalArgument(); + return; + } + PushDouble( GetTDist( T, fDF, ( bCumulative ? 4 : 3 ) ) ); +} + +void ScInterpreter::ScFDist() +{ + if ( !MustHaveParamCount( GetByte(), 3 ) ) + return; + double fF2 = ::rtl::math::approxFloor(GetDouble()); + double fF1 = ::rtl::math::approxFloor(GetDouble()); + double fF = GetDouble(); + if (fF < 0.0 || fF1 < 1.0 || fF2 < 1.0 || fF1 >= 1.0E10 || fF2 >= 1.0E10) + { + PushIllegalArgument(); + return; + } + PushDouble(GetFDist(fF, fF1, fF2)); +} + +void ScInterpreter::ScFDist_LT() +{ + int nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 3, 4 ) ) + return; + bool bCum; + if ( nParamCount == 3 ) + bCum = true; + else if ( IsMissing() ) + { + bCum = true; + Pop(); + } + else + bCum = GetBool(); + double fF2 = ::rtl::math::approxFloor( GetDouble() ); + double fF1 = ::rtl::math::approxFloor( GetDouble() ); + double fF = GetDouble(); + if ( fF < 0.0 || fF1 < 1.0 || fF2 < 1.0 || fF1 >= 1.0E10 || fF2 >= 1.0E10 ) + { + PushIllegalArgument(); + return; + } + if ( bCum ) + { + // left tail cumulative distribution + PushDouble( 1.0 - GetFDist( fF, fF1, fF2 ) ); + } + else + { + // probability density function + PushDouble( pow( fF1 / fF2, fF1 / 2 ) * pow( fF, ( fF1 / 2 ) - 1 ) / + ( pow( ( 1 + ( fF * fF1 / fF2 ) ), ( fF1 + fF2 ) / 2 ) * + GetBeta( fF1 / 2, fF2 / 2 ) ) ); + } +} + +void ScInterpreter::ScChiDist( bool bODFF ) +{ + double fResult; + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + double fDF = ::rtl::math::approxFloor(GetDouble()); + double fChi = GetDouble(); + if ( fDF < 1.0 // x<=0 returns 1, see ODFF1.2 6.18.11 + || ( !bODFF && fChi < 0 ) ) // Excel does not accept negative fChi + { + PushIllegalArgument(); + return; + } + fResult = GetChiDist( fChi, fDF); + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + PushDouble(fResult); +} + +void ScInterpreter::ScWeibull() +{ + if ( !MustHaveParamCount( GetByte(), 4 ) ) + return; + + double kum = GetDouble(); // 0 or 1 + double beta = GetDouble(); // beta + double alpha = GetDouble(); // alpha + double x = GetDouble(); // x + if (alpha <= 0.0 || beta <= 0.0 || x < 0.0) + PushIllegalArgument(); + else if (kum == 0.0) // Density + PushDouble(alpha/pow(beta,alpha)*pow(x,alpha-1.0)* + exp(-pow(x/beta,alpha))); + else // Distribution + PushDouble(1.0 - exp(-pow(x/beta,alpha))); +} + +void ScInterpreter::ScPoissonDist( bool bODFF ) +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, ( bODFF ? 2 : 3 ), 3 ) ) + return; + + bool bCumulative = nParamCount != 3 || GetBool(); // default cumulative + double lambda = GetDouble(); // Mean + double x = ::rtl::math::approxFloor(GetDouble()); // discrete distribution + if (lambda <= 0.0 || x < 0.0) + PushIllegalArgument(); + else if (!bCumulative) // Probability mass function + { + if (lambda >712.0) // underflow in exp(-lambda) + { // accuracy 11 Digits + PushDouble( exp(x*log(lambda)-lambda-GetLogGamma(x+1.0))); + } + else + { + double fPoissonVar = 1.0; + for ( double f = 0.0; f < x; ++f ) + fPoissonVar *= lambda / ( f + 1.0 ); + PushDouble( fPoissonVar * exp( -lambda ) ); + } + } + else // Cumulative distribution function + { + if (lambda > 712.0) // underflow in exp(-lambda) + { // accuracy 12 Digits + PushDouble(GetUpRegIGamma(x+1.0,lambda)); + } + else + { + if (x >= 936.0) // result is always indistinguishable from 1 + PushDouble (1.0); + else + { + double fSummand = std::exp(-lambda); + KahanSum fSum = fSummand; + int nEnd = sal::static_int_cast<int>( x ); + for (int i = 1; i <= nEnd; i++) + { + fSummand = (fSummand * lambda)/static_cast<double>(i); + fSum += fSummand; + } + PushDouble(fSum.get()); + } + } + } +} + +/** Local function used in the calculation of the hypergeometric distribution. + */ +static void lcl_PutFactorialElements( ::std::vector< double >& cn, double fLower, double fUpper, double fBase ) +{ + for ( double i = fLower; i <= fUpper; ++i ) + { + double fVal = fBase - i; + if ( fVal > 1.0 ) + cn.push_back( fVal ); + } +} + +/** Calculates a value of the hypergeometric distribution. + + @see #i47296# + + This function has an extra argument bCumulative, + which only calculates the non-cumulative distribution and + which is optional in Calc and mandatory with Excel's HYPGEOM.DIST() + + @see fdo#71722 + @see tdf#102948, make Calc function ODFF1.2-compliant + @see tdf#117041, implement note at bottom of ODFF1.2 par.6.18.37 + */ +void ScInterpreter::ScHypGeomDist( int nMinParamCount ) +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, nMinParamCount, 5 ) ) + return; + + bool bCumulative = ( nParamCount == 5 && GetBool() ); + double N = ::rtl::math::approxFloor(GetDouble()); + double M = ::rtl::math::approxFloor(GetDouble()); + double n = ::rtl::math::approxFloor(GetDouble()); + double x = ::rtl::math::approxFloor(GetDouble()); + + if ( (x < 0.0) || (n < x) || (N < n) || (N < M) || (M < 0.0) ) + { + PushIllegalArgument(); + return; + } + + KahanSum fVal = 0.0; + + for ( int i = ( bCumulative ? 0 : x ); i <= x && nGlobalError == FormulaError::NONE; i++ ) + { + if ( (n - i <= N - M) && (i <= M) ) + fVal += GetHypGeomDist( i, n, M, N ); + } + + PushDouble( fVal.get() ); +} + +/** Calculates a value of the hypergeometric distribution. + + The algorithm is designed to avoid unnecessary multiplications and division + by expanding all factorial elements (9 of them). It is done by excluding + those ranges that overlap in the numerator and the denominator. This allows + for a fast calculation for large values which would otherwise cause an overflow + in the intermediate values. + + @see #i47296# + */ +double ScInterpreter::GetHypGeomDist( double x, double n, double M, double N ) +{ + const size_t nMaxArraySize = 500000; // arbitrary max array size + + std::vector<double> cnNumer, cnDenom; + + size_t nEstContainerSize = static_cast<size_t>( x + ::std::min( n, M ) ); + size_t nMaxSize = ::std::min( cnNumer.max_size(), nMaxArraySize ); + if ( nEstContainerSize > nMaxSize ) + { + PushNoValue(); + return 0; + } + cnNumer.reserve( nEstContainerSize + 10 ); + cnDenom.reserve( nEstContainerSize + 10 ); + + // Trim coefficient C first + double fCNumVarUpper = N - n - M + x - 1.0; + double fCDenomVarLower = 1.0; + if ( N - n - M + x >= M - x + 1.0 ) + { + fCNumVarUpper = M - x - 1.0; + fCDenomVarLower = N - n - 2.0*(M - x) + 1.0; + } + + double fCNumLower = N - n - fCNumVarUpper; + double fCDenomUpper = N - n - M + x + 1.0 - fCDenomVarLower; + + double fDNumVarLower = n - M; + + if ( n >= M + 1.0 ) + { + if ( N - M < n + 1.0 ) + { + // Case 1 + + if ( N - n < n + 1.0 ) + { + // no overlap + lcl_PutFactorialElements( cnNumer, 0.0, fCNumVarUpper, N - n ); + lcl_PutFactorialElements( cnDenom, 0.0, N - n - 1.0, N ); + } + else + { + // overlap + OSL_ENSURE( fCNumLower < n + 1.0, "ScHypGeomDist: wrong assertion" ); + lcl_PutFactorialElements( cnNumer, N - 2.0*n, fCNumVarUpper, N - n ); + lcl_PutFactorialElements( cnDenom, 0.0, n - 1.0, N ); + } + + OSL_ENSURE( fCDenomUpper <= N - M, "ScHypGeomDist: wrong assertion" ); + + if ( fCDenomUpper < n - x + 1.0 ) + // no overlap + lcl_PutFactorialElements( cnNumer, 1.0, N - M - n + x, N - M + 1.0 ); + else + { + // overlap + lcl_PutFactorialElements( cnNumer, 1.0, N - M - fCDenomUpper, N - M + 1.0 ); + + fCDenomUpper = n - x; + fCDenomVarLower = N - M - 2.0*(n - x) + 1.0; + } + } + else + { + // Case 2 + + if ( n > M - 1.0 ) + { + // no overlap + lcl_PutFactorialElements( cnNumer, 0.0, fCNumVarUpper, N - n ); + lcl_PutFactorialElements( cnDenom, 0.0, M - 1.0, N ); + } + else + { + lcl_PutFactorialElements( cnNumer, M - n, fCNumVarUpper, N - n ); + lcl_PutFactorialElements( cnDenom, 0.0, n - 1.0, N ); + } + + OSL_ENSURE( fCDenomUpper <= n, "ScHypGeomDist: wrong assertion" ); + + if ( fCDenomUpper < n - x + 1.0 ) + // no overlap + lcl_PutFactorialElements( cnNumer, N - M - n + 1.0, N - M - n + x, N - M + 1.0 ); + else + { + lcl_PutFactorialElements( cnNumer, N - M - n + 1.0, N - M - fCDenomUpper, N - M + 1.0 ); + fCDenomUpper = n - x; + fCDenomVarLower = N - M - 2.0*(n - x) + 1.0; + } + } + + OSL_ENSURE( fCDenomUpper <= M, "ScHypGeomDist: wrong assertion" ); + } + else + { + if ( N - M < M + 1.0 ) + { + // Case 3 + + if ( N - n < M + 1.0 ) + { + // No overlap + lcl_PutFactorialElements( cnNumer, 0.0, fCNumVarUpper, N - n ); + lcl_PutFactorialElements( cnDenom, 0.0, N - M - 1.0, N ); + } + else + { + lcl_PutFactorialElements( cnNumer, N - n - M, fCNumVarUpper, N - n ); + lcl_PutFactorialElements( cnDenom, 0.0, n - 1.0, N ); + } + + if ( n - x + 1.0 > fCDenomUpper ) + // No overlap + lcl_PutFactorialElements( cnNumer, 1.0, N - M - n + x, N - M + 1.0 ); + else + { + // Overlap + lcl_PutFactorialElements( cnNumer, 1.0, N - M - fCDenomUpper, N - M + 1.0 ); + + fCDenomVarLower = N - M - 2.0*(n - x) + 1.0; + fCDenomUpper = n - x; + } + } + else + { + // Case 4 + + OSL_ENSURE( M >= n - x, "ScHypGeomDist: wrong assertion" ); + OSL_ENSURE( M - x <= N - M + 1.0, "ScHypGeomDist: wrong assertion" ); + + if ( N - n < N - M + 1.0 ) + { + // No overlap + lcl_PutFactorialElements( cnNumer, 0.0, fCNumVarUpper, N - n ); + lcl_PutFactorialElements( cnDenom, 0.0, M - 1.0, N ); + } + else + { + // Overlap + OSL_ENSURE( fCNumLower <= N - M + 1.0, "ScHypGeomDist: wrong assertion" ); + lcl_PutFactorialElements( cnNumer, M - n, fCNumVarUpper, N - n ); + lcl_PutFactorialElements( cnDenom, 0.0, n - 1.0, N ); + } + + if ( n - x + 1.0 > fCDenomUpper ) + // No overlap + lcl_PutFactorialElements( cnNumer, N - 2.0*M + 1.0, N - M - n + x, N - M + 1.0 ); + else if ( M >= fCDenomUpper ) + { + lcl_PutFactorialElements( cnNumer, N - 2.0*M + 1.0, N - M - fCDenomUpper, N - M + 1.0 ); + + fCDenomUpper = n - x; + fCDenomVarLower = N - M - 2.0*(n - x) + 1.0; + } + else + { + OSL_ENSURE( M <= fCDenomUpper, "ScHypGeomDist: wrong assertion" ); + lcl_PutFactorialElements( cnDenom, fCDenomVarLower, N - n - 2.0*M + x, + N - n - M + x + 1.0 ); + + fCDenomUpper = n - x; + fCDenomVarLower = N - M - 2.0*(n - x) + 1.0; + } + } + + OSL_ENSURE( fCDenomUpper <= n, "ScHypGeomDist: wrong assertion" ); + + fDNumVarLower = 0.0; + } + + double nDNumVarUpper = fCDenomUpper < x + 1.0 ? n - x - 1.0 : n - fCDenomUpper - 1.0; + double nDDenomVarLower = fCDenomUpper < x + 1.0 ? fCDenomVarLower : N - n - M + 1.0; + lcl_PutFactorialElements( cnNumer, fDNumVarLower, nDNumVarUpper, n ); + lcl_PutFactorialElements( cnDenom, nDDenomVarLower, N - n - M + x, N - n - M + x + 1.0 ); + + ::std::sort( cnNumer.begin(), cnNumer.end() ); + ::std::sort( cnDenom.begin(), cnDenom.end() ); + auto it1 = cnNumer.rbegin(), it1End = cnNumer.rend(); + auto it2 = cnDenom.rbegin(), it2End = cnDenom.rend(); + + double fFactor = 1.0; + for ( ; it1 != it1End || it2 != it2End; ) + { + double fEnum = 1.0, fDenom = 1.0; + if ( it1 != it1End ) + fEnum = *it1++; + if ( it2 != it2End ) + fDenom = *it2++; + fFactor *= fEnum / fDenom; + } + + return fFactor; +} + +void ScInterpreter::ScGammaDist( bool bODFF ) +{ + sal_uInt8 nMinParamCount = ( bODFF ? 3 : 4 ); + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, nMinParamCount, 4 ) ) + return; + bool bCumulative; + if (nParamCount == 4) + bCumulative = GetBool(); + else + bCumulative = true; + double fBeta = GetDouble(); // scale + double fAlpha = GetDouble(); // shape + double fX = GetDouble(); // x + if ((!bODFF && fX < 0) || fAlpha <= 0.0 || fBeta <= 0.0) + PushIllegalArgument(); + else + { + if (bCumulative) // distribution + PushDouble( GetGammaDist( fX, fAlpha, fBeta)); + else // density + PushDouble( GetGammaDistPDF( fX, fAlpha, fBeta)); + } +} + +void ScInterpreter::ScNormInv() +{ + if ( MustHaveParamCount( GetByte(), 3 ) ) + { + double sigma = GetDouble(); + double mue = GetDouble(); + double x = GetDouble(); + if (sigma <= 0.0 || x < 0.0 || x > 1.0) + PushIllegalArgument(); + else if (x == 0.0 || x == 1.0) + PushNoValue(); + else + PushDouble(gaussinv(x)*sigma + mue); + } +} + +void ScInterpreter::ScSNormInv() +{ + double x = GetDouble(); + if (x < 0.0 || x > 1.0) + PushIllegalArgument(); + else if (x == 0.0 || x == 1.0) + PushNoValue(); + else + PushDouble(gaussinv(x)); +} + +void ScInterpreter::ScLogNormInv() +{ + sal_uInt8 nParamCount = GetByte(); + if ( MustHaveParamCount( nParamCount, 1, 3 ) ) + { + double fSigma = ( nParamCount == 3 ? GetDouble() : 1.0 ); // Stddev + double fMue = ( nParamCount >= 2 ? GetDouble() : 0.0 ); // Mean + double fP = GetDouble(); // p + if ( fSigma <= 0.0 || fP <= 0.0 || fP >= 1.0 ) + PushIllegalArgument(); + else + PushDouble( exp( fMue + fSigma * gaussinv( fP ) ) ); + } +} + +class ScGammaDistFunction : public ScDistFunc +{ + ScInterpreter& rInt; + double fp, fAlpha, fBeta; + +public: + ScGammaDistFunction( ScInterpreter& rI, double fpVal, double fAlphaVal, double fBetaVal ) : + rInt(rI), fp(fpVal), fAlpha(fAlphaVal), fBeta(fBetaVal) {} + + virtual ~ScGammaDistFunction() {} + + double GetValue( double x ) const override { return fp - rInt.GetGammaDist(x, fAlpha, fBeta); } +}; + +void ScInterpreter::ScGammaInv() +{ + if ( !MustHaveParamCount( GetByte(), 3 ) ) + return; + double fBeta = GetDouble(); + double fAlpha = GetDouble(); + double fP = GetDouble(); + if (fAlpha <= 0.0 || fBeta <= 0.0 || fP < 0.0 || fP >= 1.0 ) + { + PushIllegalArgument(); + return; + } + if (fP == 0.0) + PushInt(0); + else + { + bool bConvError; + ScGammaDistFunction aFunc( *this, fP, fAlpha, fBeta ); + double fStart = fAlpha * fBeta; + double fVal = lcl_IterateInverse( aFunc, fStart*0.5, fStart, bConvError ); + if (bConvError) + SetError(FormulaError::NoConvergence); + PushDouble(fVal); + } +} + +class ScBetaDistFunction : public ScDistFunc +{ + ScInterpreter& rInt; + double fp, fAlpha, fBeta; + +public: + ScBetaDistFunction( ScInterpreter& rI, double fpVal, double fAlphaVal, double fBetaVal ) : + rInt(rI), fp(fpVal), fAlpha(fAlphaVal), fBeta(fBetaVal) {} + + virtual ~ScBetaDistFunction() {} + + double GetValue( double x ) const override { return fp - rInt.GetBetaDist(x, fAlpha, fBeta); } +}; + +void ScInterpreter::ScBetaInv() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 3, 5 ) ) + return; + double fP, fA, fB, fAlpha, fBeta; + if (nParamCount == 5) + fB = GetDouble(); + else + fB = 1.0; + if (nParamCount >= 4) + fA = GetDouble(); + else + fA = 0.0; + fBeta = GetDouble(); + fAlpha = GetDouble(); + fP = GetDouble(); + if (fP < 0.0 || fP > 1.0 || fA >= fB || fAlpha <= 0.0 || fBeta <= 0.0) + { + PushIllegalArgument(); + return; + } + + bool bConvError; + ScBetaDistFunction aFunc( *this, fP, fAlpha, fBeta ); + // 0..1 as range for iteration so it isn't extended beyond the valid range + double fVal = lcl_IterateInverse( aFunc, 0.0, 1.0, bConvError ); + if (bConvError) + PushError( FormulaError::NoConvergence); + else + PushDouble(fA + fVal*(fB-fA)); // scale to (A,B) +} + +// Note: T, F, and Chi are +// monotonically decreasing, +// therefore 1-Dist as function + +class ScTDistFunction : public ScDistFunc +{ + ScInterpreter& rInt; + double fp, fDF; + int nT; + +public: + ScTDistFunction( ScInterpreter& rI, double fpVal, double fDFVal, int nType ) : + rInt( rI ), fp( fpVal ), fDF( fDFVal ), nT( nType ) {} + + virtual ~ScTDistFunction() {} + + double GetValue( double x ) const override { return fp - rInt.GetTDist( x, fDF, nT ); } +}; + +void ScInterpreter::ScTInv( int nType ) +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + double fDF = ::rtl::math::approxFloor(GetDouble()); + double fP = GetDouble(); + if (fDF < 1.0 || fP <= 0.0 || fP > 1.0 ) + { + PushIllegalArgument(); + return; + } + if ( nType == 4 ) // left-tailed cumulative t-distribution + { + if ( fP == 1.0 ) + PushIllegalArgument(); + else if ( fP < 0.5 ) + PushDouble( -GetTInv( 1 - fP, fDF, nType ) ); + else + PushDouble( GetTInv( fP, fDF, nType ) ); + } + else + PushDouble( GetTInv( fP, fDF, nType ) ); +}; + +double ScInterpreter::GetTInv( double fAlpha, double fSize, int nType ) +{ + bool bConvError; + ScTDistFunction aFunc( *this, fAlpha, fSize, nType ); + double fVal = lcl_IterateInverse( aFunc, fSize * 0.5, fSize, bConvError ); + if (bConvError) + SetError(FormulaError::NoConvergence); + return fVal; +} + +class ScFDistFunction : public ScDistFunc +{ + ScInterpreter& rInt; + double fp, fF1, fF2; + +public: + ScFDistFunction( ScInterpreter& rI, double fpVal, double fF1Val, double fF2Val ) : + rInt(rI), fp(fpVal), fF1(fF1Val), fF2(fF2Val) {} + + virtual ~ScFDistFunction() {} + + double GetValue( double x ) const override { return fp - rInt.GetFDist(x, fF1, fF2); } +}; + +void ScInterpreter::ScFInv() +{ + if ( !MustHaveParamCount( GetByte(), 3 ) ) + return; + double fF2 = ::rtl::math::approxFloor(GetDouble()); + double fF1 = ::rtl::math::approxFloor(GetDouble()); + double fP = GetDouble(); + if (fP <= 0.0 || fF1 < 1.0 || fF2 < 1.0 || fF1 >= 1.0E10 || fF2 >= 1.0E10 || fP > 1.0) + { + PushIllegalArgument(); + return; + } + + bool bConvError; + ScFDistFunction aFunc( *this, fP, fF1, fF2 ); + double fVal = lcl_IterateInverse( aFunc, fF1*0.5, fF1, bConvError ); + if (bConvError) + SetError(FormulaError::NoConvergence); + PushDouble(fVal); +} + +void ScInterpreter::ScFInv_LT() +{ + if ( !MustHaveParamCount( GetByte(), 3 ) ) + return; + double fF2 = ::rtl::math::approxFloor(GetDouble()); + double fF1 = ::rtl::math::approxFloor(GetDouble()); + double fP = GetDouble(); + if (fP <= 0.0 || fF1 < 1.0 || fF2 < 1.0 || fF1 >= 1.0E10 || fF2 >= 1.0E10 || fP > 1.0) + { + PushIllegalArgument(); + return; + } + + bool bConvError; + ScFDistFunction aFunc( *this, ( 1.0 - fP ), fF1, fF2 ); + double fVal = lcl_IterateInverse( aFunc, fF1*0.5, fF1, bConvError ); + if (bConvError) + SetError(FormulaError::NoConvergence); + PushDouble(fVal); +} + +class ScChiDistFunction : public ScDistFunc +{ + ScInterpreter& rInt; + double fp, fDF; + +public: + ScChiDistFunction( ScInterpreter& rI, double fpVal, double fDFVal ) : + rInt(rI), fp(fpVal), fDF(fDFVal) {} + + virtual ~ScChiDistFunction() {} + + double GetValue( double x ) const override { return fp - rInt.GetChiDist(x, fDF); } +}; + +void ScInterpreter::ScChiInv() +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + double fDF = ::rtl::math::approxFloor(GetDouble()); + double fP = GetDouble(); + if (fDF < 1.0 || fP <= 0.0 || fP > 1.0 ) + { + PushIllegalArgument(); + return; + } + + bool bConvError; + ScChiDistFunction aFunc( *this, fP, fDF ); + double fVal = lcl_IterateInverse( aFunc, fDF*0.5, fDF, bConvError ); + if (bConvError) + SetError(FormulaError::NoConvergence); + PushDouble(fVal); +} + +/***********************************************/ +class ScChiSqDistFunction : public ScDistFunc +{ + ScInterpreter& rInt; + double fp, fDF; + +public: + ScChiSqDistFunction( ScInterpreter& rI, double fpVal, double fDFVal ) : + rInt(rI), fp(fpVal), fDF(fDFVal) {} + + virtual ~ScChiSqDistFunction() {} + + double GetValue( double x ) const override { return fp - rInt.GetChiSqDistCDF(x, fDF); } +}; + +void ScInterpreter::ScChiSqInv() +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + double fDF = ::rtl::math::approxFloor(GetDouble()); + double fP = GetDouble(); + if (fDF < 1.0 || fP < 0.0 || fP >= 1.0 ) + { + PushIllegalArgument(); + return; + } + + bool bConvError; + ScChiSqDistFunction aFunc( *this, fP, fDF ); + double fVal = lcl_IterateInverse( aFunc, fDF*0.5, fDF, bConvError ); + if (bConvError) + SetError(FormulaError::NoConvergence); + PushDouble(fVal); +} + +void ScInterpreter::ScConfidence() +{ + if ( MustHaveParamCount( GetByte(), 3 ) ) + { + double n = ::rtl::math::approxFloor(GetDouble()); + double sigma = GetDouble(); + double alpha = GetDouble(); + if (sigma <= 0.0 || alpha <= 0.0 || alpha >= 1.0 || n < 1.0) + PushIllegalArgument(); + else + PushDouble( gaussinv(1.0-alpha/2.0) * sigma/sqrt(n) ); + } +} + +void ScInterpreter::ScConfidenceT() +{ + if ( MustHaveParamCount( GetByte(), 3 ) ) + { + double n = ::rtl::math::approxFloor(GetDouble()); + double sigma = GetDouble(); + double alpha = GetDouble(); + if (sigma <= 0.0 || alpha <= 0.0 || alpha >= 1.0 || n < 1.0) + PushIllegalArgument(); + else if (n == 1.0) // for interoperability with Excel + PushError(FormulaError::DivisionByZero); + else + PushDouble( sigma * GetTInv( alpha, n - 1, 2 ) / sqrt( n ) ); + } +} + +void ScInterpreter::ScZTest() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2, 3 ) ) + return; + double sigma = 0.0, x; + if (nParamCount == 3) + { + sigma = GetDouble(); + if (sigma <= 0.0) + { + PushIllegalArgument(); + return; + } + } + x = GetDouble(); + + KahanSum fSum = 0.0; + KahanSum fSumSqr = 0.0; + double fVal; + double rValCount = 0.0; + switch (GetStackType()) + { + case svDouble : + { + fVal = GetDouble(); + fSum += fVal; + fSumSqr += fVal*fVal; + rValCount++; + } + break; + case svSingleRef : + { + ScAddress aAdr; + PopSingleRef( aAdr ); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + { + fVal = GetCellValue(aAdr, aCell); + fSum += fVal; + fSumSqr += fVal*fVal; + rValCount++; + } + } + break; + case svRefList : + case svDoubleRef : + { + short nParam = 1; + size_t nRefInList = 0; + while (nParam-- > 0) + { + ScRange aRange; + FormulaError nErr = FormulaError::NONE; + PopDoubleRef( aRange, nParam, nRefInList); + ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags ); + if (aValIter.GetFirst(fVal, nErr)) + { + fSum += fVal; + fSumSqr += fVal*fVal; + rValCount++; + while ((nErr == FormulaError::NONE) && aValIter.GetNext(fVal, nErr)) + { + fSum += fVal; + fSumSqr += fVal*fVal; + rValCount++; + } + SetError(nErr); + } + } + } + break; + case svMatrix : + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatrixRef pMat = GetMatrix(); + if (pMat) + { + SCSIZE nCount = pMat->GetElementCount(); + if (pMat->IsNumeric()) + { + for ( SCSIZE i = 0; i < nCount; i++ ) + { + fVal= pMat->GetDouble(i); + fSum += fVal; + fSumSqr += fVal * fVal; + rValCount++; + } + } + else + { + for (SCSIZE i = 0; i < nCount; i++) + if (!pMat->IsStringOrEmpty(i)) + { + fVal= pMat->GetDouble(i); + fSum += fVal; + fSumSqr += fVal * fVal; + rValCount++; + } + } + } + } + break; + default : SetError(FormulaError::IllegalParameter); break; + } + if (rValCount <= 1.0) + PushError( FormulaError::DivisionByZero); + else + { + double mue = fSum.get()/rValCount; + + if (nParamCount != 3) + { + sigma = (fSumSqr - fSum*fSum/rValCount).get()/(rValCount-1.0); + if (sigma == 0.0) + { + PushError(FormulaError::DivisionByZero); + return; + } + PushDouble(0.5 - gauss((mue-x)/sqrt(sigma/rValCount))); + } + else + PushDouble(0.5 - gauss((mue-x)*sqrt(rValCount)/sigma)); + } +} + +bool ScInterpreter::CalculateTest(bool _bTemplin + ,const SCSIZE nC1, const SCSIZE nC2,const SCSIZE nR1,const SCSIZE nR2 + ,const ScMatrixRef& pMat1,const ScMatrixRef& pMat2 + ,double& fT,double& fF) +{ + double fCount1 = 0.0; + double fCount2 = 0.0; + KahanSum fSum1 = 0.0; + KahanSum fSumSqr1 = 0.0; + KahanSum fSum2 = 0.0; + KahanSum fSumSqr2 = 0.0; + double fVal; + SCSIZE i,j; + for (i = 0; i < nC1; i++) + for (j = 0; j < nR1; j++) + { + if (!pMat1->IsStringOrEmpty(i,j)) + { + fVal = pMat1->GetDouble(i,j); + fSum1 += fVal; + fSumSqr1 += fVal * fVal; + fCount1++; + } + } + for (i = 0; i < nC2; i++) + for (j = 0; j < nR2; j++) + { + if (!pMat2->IsStringOrEmpty(i,j)) + { + fVal = pMat2->GetDouble(i,j); + fSum2 += fVal; + fSumSqr2 += fVal * fVal; + fCount2++; + } + } + if (fCount1 < 2.0 || fCount2 < 2.0) + { + PushNoValue(); + return false; + } // if (fCount1 < 2.0 || fCount2 < 2.0) + if ( _bTemplin ) + { + double fS1 = (fSumSqr1-fSum1*fSum1/fCount1).get() / (fCount1-1.0) / fCount1; + double fS2 = (fSumSqr2-fSum2*fSum2/fCount2).get() / (fCount2-1.0) / fCount2; + if (fS1 + fS2 == 0.0) + { + PushNoValue(); + return false; + } + fT = std::abs(( fSum1/fCount1 - fSum2/fCount2 ).get())/sqrt(fS1+fS2); + double c = fS1/(fS1+fS2); + // GetTDist is calculated via GetBetaDist and also works with non-integral + // degrees of freedom. The result matches Excel + fF = 1.0/(c*c/(fCount1-1.0)+(1.0-c)*(1.0-c)/(fCount2-1.0)); + } + else + { + // according to Bronstein-Semendjajew + double fS1 = (fSumSqr1 - fSum1*fSum1/fCount1).get() / (fCount1 - 1.0); // Variance + double fS2 = (fSumSqr2 - fSum2*fSum2/fCount2).get() / (fCount2 - 1.0); + fT = std::abs( fSum1.get()/fCount1 - fSum2.get()/fCount2 ) / + sqrt( (fCount1-1.0)*fS1 + (fCount2-1.0)*fS2 ) * + sqrt( fCount1*fCount2*(fCount1+fCount2-2)/(fCount1+fCount2) ); + fF = fCount1 + fCount2 - 2; + } + return true; +} +void ScInterpreter::ScTTest() +{ + if ( !MustHaveParamCount( GetByte(), 4 ) ) + return; + double fTyp = ::rtl::math::approxFloor(GetDouble()); + double fTails = ::rtl::math::approxFloor(GetDouble()); + if (fTails != 1.0 && fTails != 2.0) + { + PushIllegalArgument(); + return; + } + + ScMatrixRef pMat2 = GetMatrix(); + ScMatrixRef pMat1 = GetMatrix(); + if (!pMat1 || !pMat2) + { + PushIllegalParameter(); + return; + } + double fT, fF; + SCSIZE nC1, nC2; + SCSIZE nR1, nR2; + SCSIZE i, j; + pMat1->GetDimensions(nC1, nR1); + pMat2->GetDimensions(nC2, nR2); + if (fTyp == 1.0) + { + if (nC1 != nC2 || nR1 != nR2) + { + PushIllegalArgument(); + return; + } + double fCount = 0.0; + KahanSum fSum1 = 0.0; + KahanSum fSum2 = 0.0; + KahanSum fSumSqrD = 0.0; + double fVal1, fVal2; + for (i = 0; i < nC1; i++) + for (j = 0; j < nR1; j++) + { + if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j)) + { + fVal1 = pMat1->GetDouble(i,j); + fVal2 = pMat2->GetDouble(i,j); + fSum1 += fVal1; + fSum2 += fVal2; + fSumSqrD += (fVal1 - fVal2)*(fVal1 - fVal2); + fCount++; + } + } + if (fCount < 1.0) + { + PushNoValue(); + return; + } + KahanSum fSumD = fSum1 - fSum2; + double fDivider = ( fSumSqrD*fCount - fSumD*fSumD ).get(); + if ( fDivider == 0.0 ) + { + PushError(FormulaError::DivisionByZero); + return; + } + fT = std::abs(fSumD.get()) * sqrt((fCount-1.0) / fDivider); + fF = fCount - 1.0; + } + else if (fTyp == 2.0) + { + if (!CalculateTest(false,nC1, nC2,nR1, nR2,pMat1,pMat2,fT,fF)) + return; // error was pushed + } + else if (fTyp == 3.0) + { + if (!CalculateTest(true,nC1, nC2,nR1, nR2,pMat1,pMat2,fT,fF)) + return; // error was pushed + } + else + { + PushIllegalArgument(); + return; + } + PushDouble( GetTDist( fT, fF, static_cast<int>(fTails) ) ); +} + +void ScInterpreter::ScFTest() +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + ScMatrixRef pMat2 = GetMatrix(); + ScMatrixRef pMat1 = GetMatrix(); + if (!pMat1 || !pMat2) + { + PushIllegalParameter(); + return; + } + + auto aVal1 = pMat1->CollectKahan(sc::op::kOpSumAndSumSquare); + auto aVal2 = pMat2->CollectKahan(sc::op::kOpSumAndSumSquare); + double fCount1 = aVal1.mnCount; + double fCount2 = aVal2.mnCount; + KahanSum fSum1 = aVal1.maAccumulator[0]; + KahanSum fSumSqr1 = aVal1.maAccumulator[1]; + KahanSum fSum2 = aVal2.maAccumulator[0]; + KahanSum fSumSqr2 = aVal2.maAccumulator[1]; + + if (fCount1 < 2.0 || fCount2 < 2.0) + { + PushNoValue(); + return; + } + double fS1 = (fSumSqr1-fSum1*fSum1/fCount1).get() / (fCount1-1.0); + double fS2 = (fSumSqr2-fSum2*fSum2/fCount2).get() / (fCount2-1.0); + if (fS1 == 0.0 || fS2 == 0.0) + { + PushNoValue(); + return; + } + double fF, fF1, fF2; + if (fS1 > fS2) + { + fF = fS1/fS2; + fF1 = fCount1-1.0; + fF2 = fCount2-1.0; + } + else + { + fF = fS2/fS1; + fF1 = fCount2-1.0; + fF2 = fCount1-1.0; + } + double fFcdf = GetFDist(fF, fF1, fF2); + PushDouble(2.0*std::min(fFcdf, 1.0 - fFcdf)); +} + +void ScInterpreter::ScChiTest() +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + ScMatrixRef pMat2 = GetMatrix(); + ScMatrixRef pMat1 = GetMatrix(); + if (!pMat1 || !pMat2) + { + PushIllegalParameter(); + return; + } + SCSIZE nC1, nC2; + SCSIZE nR1, nR2; + pMat1->GetDimensions(nC1, nR1); + pMat2->GetDimensions(nC2, nR2); + if (nR1 != nR2 || nC1 != nC2) + { + PushIllegalArgument(); + return; + } + KahanSum fChi = 0.0; + bool bEmpty = true; + for (SCSIZE i = 0; i < nC1; i++) + { + for (SCSIZE j = 0; j < nR1; j++) + { + if (!(pMat1->IsEmpty(i,j) || pMat2->IsEmpty(i,j))) + { + bEmpty = false; + if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j)) + { + double fValX = pMat1->GetDouble(i,j); + double fValE = pMat2->GetDouble(i,j); + if ( fValE == 0.0 ) + { + PushError(FormulaError::DivisionByZero); + return; + } + // These fTemp values guard against a failure when compiled + // with optimization (using g++ 4.8.2 on tinderbox 71-TDF), + // where ((fValX - fValE) * (fValX - fValE)) with + // fValE==1e+308 should had produced Infinity but did + // not, instead the result of divide() then was 1e+308. + volatile double fTemp1 = (fValX - fValE) * (fValX - fValE); + double fTemp2 = fTemp1; + if (std::isinf(fTemp2)) + { + PushError(FormulaError::NoConvergence); + return; + } + fChi += sc::divide( fTemp2, fValE); + } + else + { + PushIllegalArgument(); + return; + } + } + } + } + if ( bEmpty ) + { + // not in ODFF1.2, but for interoperability with Excel + PushIllegalArgument(); + return; + } + double fDF; + if (nC1 == 1 || nR1 == 1) + { + fDF = static_cast<double>(nC1*nR1 - 1); + if (fDF == 0.0) + { + PushNoValue(); + return; + } + } + else + fDF = static_cast<double>(nC1-1)*static_cast<double>(nR1-1); + PushDouble(GetChiDist(fChi.get(), fDF)); +} + +void ScInterpreter::ScKurt() +{ + KahanSum fSum; + double fCount; + std::vector<double> values; + if ( !CalculateSkew(fSum, fCount, values) ) + return; + + // ODF 1.2 constraints: # of numbers >= 4 + if (fCount < 4.0) + { + // for interoperability with Excel + PushError( FormulaError::DivisionByZero); + return; + } + + KahanSum vSum; + double fMean = fSum.get() / fCount; + for (double v : values) + vSum += (v - fMean) * (v - fMean); + + double fStdDev = sqrt(vSum.get() / (fCount - 1.0)); + if (fStdDev == 0.0) + { + PushError( FormulaError::DivisionByZero); + return; + } + + KahanSum xpower4 = 0.0; + for (double v : values) + { + double dx = (v - fMean) / fStdDev; + xpower4 += (dx * dx) * (dx * dx); + } + + double k_d = (fCount - 2.0) * (fCount - 3.0); + double k_l = fCount * (fCount + 1.0) / ((fCount - 1.0) * k_d); + double k_t = 3.0 * (fCount - 1.0) * (fCount - 1.0) / k_d; + + PushDouble(xpower4.get() * k_l - k_t); +} + +void ScInterpreter::ScHarMean() +{ + short nParamCount = GetByte(); + KahanSum nVal = 0.0; + double nValCount = 0.0; + ScAddress aAdr; + ScRange aRange; + size_t nRefInList = 0; + while ((nGlobalError == FormulaError::NONE) && (nParamCount-- > 0)) + { + switch (GetStackType()) + { + case svDouble : + { + double x = GetDouble(); + if (x > 0.0) + { + nVal += 1.0/x; + nValCount++; + } + else + SetError( FormulaError::IllegalArgument); + break; + } + case svSingleRef : + { + PopSingleRef( aAdr ); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + { + double x = GetCellValue(aAdr, aCell); + if (x > 0.0) + { + nVal += 1.0/x; + nValCount++; + } + else + SetError( FormulaError::IllegalArgument); + } + break; + } + case svDoubleRef : + case svRefList : + { + FormulaError nErr = FormulaError::NONE; + PopDoubleRef( aRange, nParamCount, nRefInList); + double nCellVal; + ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags ); + if (aValIter.GetFirst(nCellVal, nErr)) + { + if (nCellVal > 0.0) + { + nVal += 1.0/nCellVal; + nValCount++; + } + else + SetError( FormulaError::IllegalArgument); + SetError(nErr); + while ((nErr == FormulaError::NONE) && aValIter.GetNext(nCellVal, nErr)) + { + if (nCellVal > 0.0) + { + nVal += 1.0/nCellVal; + nValCount++; + } + else + SetError( FormulaError::IllegalArgument); + } + SetError(nErr); + } + } + break; + case svMatrix : + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatrixRef pMat = GetMatrix(); + if (pMat) + { + SCSIZE nCount = pMat->GetElementCount(); + if (pMat->IsNumeric()) + { + for (SCSIZE nElem = 0; nElem < nCount; nElem++) + { + double x = pMat->GetDouble(nElem); + if (x > 0.0) + { + nVal += 1.0/x; + nValCount++; + } + else + SetError( FormulaError::IllegalArgument); + } + } + else + { + for (SCSIZE nElem = 0; nElem < nCount; nElem++) + if (!pMat->IsStringOrEmpty(nElem)) + { + double x = pMat->GetDouble(nElem); + if (x > 0.0) + { + nVal += 1.0/x; + nValCount++; + } + else + SetError( FormulaError::IllegalArgument); + } + } + } + } + break; + default : SetError(FormulaError::IllegalParameter); break; + } + } + if (nGlobalError == FormulaError::NONE) + PushDouble( nValCount / nVal.get() ); + else + PushError( nGlobalError); +} + +void ScInterpreter::ScGeoMean() +{ + short nParamCount = GetByte(); + KahanSum nVal = 0.0; + double nValCount = 0.0; + ScAddress aAdr; + ScRange aRange; + + size_t nRefInList = 0; + while ((nGlobalError == FormulaError::NONE) && (nParamCount-- > 0)) + { + switch (GetStackType()) + { + case svDouble : + { + double x = GetDouble(); + if (x > 0.0) + { + nVal += log(x); + nValCount++; + } + else if ( x == 0.0 ) + { + // value of 0 means that function result will be 0 + while ( nParamCount-- > 0 ) + PopError(); + PushDouble( 0.0 ); + return; + } + else + SetError( FormulaError::IllegalArgument); + break; + } + case svSingleRef : + { + PopSingleRef( aAdr ); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + { + double x = GetCellValue(aAdr, aCell); + if (x > 0.0) + { + nVal += log(x); + nValCount++; + } + else if ( x == 0.0 ) + { + // value of 0 means that function result will be 0 + while ( nParamCount-- > 0 ) + PopError(); + PushDouble( 0.0 ); + return; + } + else + SetError( FormulaError::IllegalArgument); + } + break; + } + case svDoubleRef : + case svRefList : + { + FormulaError nErr = FormulaError::NONE; + PopDoubleRef( aRange, nParamCount, nRefInList); + double nCellVal; + ScValueIterator aValIter(mrContext, aRange, mnSubTotalFlags); + if (aValIter.GetFirst(nCellVal, nErr)) + { + if (nCellVal > 0.0) + { + nVal += log(nCellVal); + nValCount++; + } + else if ( nCellVal == 0.0 ) + { + // value of 0 means that function result will be 0 + while ( nParamCount-- > 0 ) + PopError(); + PushDouble( 0.0 ); + return; + } + else + SetError( FormulaError::IllegalArgument); + SetError(nErr); + while ((nErr == FormulaError::NONE) && aValIter.GetNext(nCellVal, nErr)) + { + if (nCellVal > 0.0) + { + nVal += log(nCellVal); + nValCount++; + } + else if ( nCellVal == 0.0 ) + { + // value of 0 means that function result will be 0 + while ( nParamCount-- > 0 ) + PopError(); + PushDouble( 0.0 ); + return; + } + else + SetError( FormulaError::IllegalArgument); + } + SetError(nErr); + } + } + break; + case svMatrix : + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatrixRef pMat = GetMatrix(); + if (pMat) + { + SCSIZE nCount = pMat->GetElementCount(); + if (pMat->IsNumeric()) + { + for (SCSIZE ui = 0; ui < nCount; ui++) + { + double x = pMat->GetDouble(ui); + if (x > 0.0) + { + nVal += log(x); + nValCount++; + } + else if ( x == 0.0 ) + { + // value of 0 means that function result will be 0 + while ( nParamCount-- > 0 ) + PopError(); + PushDouble( 0.0 ); + return; + } + else + SetError( FormulaError::IllegalArgument); + } + } + else + { + for (SCSIZE ui = 0; ui < nCount; ui++) + { + if (!pMat->IsStringOrEmpty(ui)) + { + double x = pMat->GetDouble(ui); + if (x > 0.0) + { + nVal += log(x); + nValCount++; + } + else if ( x == 0.0 ) + { + // value of 0 means that function result will be 0 + while ( nParamCount-- > 0 ) + PopError(); + PushDouble( 0.0 ); + return; + } + else + SetError( FormulaError::IllegalArgument); + } + } + } + } + } + break; + default : SetError(FormulaError::IllegalParameter); break; + } + } + if (nGlobalError == FormulaError::NONE) + PushDouble(exp(nVal.get() / nValCount)); + else + PushError( nGlobalError); +} + +void ScInterpreter::ScStandard() +{ + if ( MustHaveParamCount( GetByte(), 3 ) ) + { + double sigma = GetDouble(); + double mue = GetDouble(); + double x = GetDouble(); + if (sigma < 0.0) + PushError( FormulaError::IllegalArgument); + else if (sigma == 0.0) + PushError( FormulaError::DivisionByZero); + else + PushDouble((x-mue)/sigma); + } +} +bool ScInterpreter::CalculateSkew(KahanSum& fSum, double& fCount, std::vector<double>& values) +{ + short nParamCount = GetByte(); + if ( !MustHaveParamCountMin( nParamCount, 1 ) ) + return false; + + fSum = 0.0; + fCount = 0.0; + double fVal = 0.0; + ScAddress aAdr; + ScRange aRange; + size_t nRefInList = 0; + while (nParamCount-- > 0) + { + switch (GetStackType()) + { + case svDouble : + { + fVal = GetDouble(); + fSum += fVal; + values.push_back(fVal); + fCount++; + } + break; + case svSingleRef : + { + PopSingleRef( aAdr ); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + { + fVal = GetCellValue(aAdr, aCell); + fSum += fVal; + values.push_back(fVal); + fCount++; + } + } + break; + case svDoubleRef : + case svRefList : + { + PopDoubleRef( aRange, nParamCount, nRefInList); + FormulaError nErr = FormulaError::NONE; + ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags ); + if (aValIter.GetFirst(fVal, nErr)) + { + fSum += fVal; + values.push_back(fVal); + fCount++; + SetError(nErr); + while ((nErr == FormulaError::NONE) && aValIter.GetNext(fVal, nErr)) + { + fSum += fVal; + values.push_back(fVal); + fCount++; + } + SetError(nErr); + } + } + break; + case svMatrix : + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatrixRef pMat = GetMatrix(); + if (pMat) + { + SCSIZE nCount = pMat->GetElementCount(); + if (pMat->IsNumeric()) + { + for (SCSIZE nElem = 0; nElem < nCount; nElem++) + { + fVal = pMat->GetDouble(nElem); + fSum += fVal; + values.push_back(fVal); + fCount++; + } + } + else + { + for (SCSIZE nElem = 0; nElem < nCount; nElem++) + if (!pMat->IsStringOrEmpty(nElem)) + { + fVal = pMat->GetDouble(nElem); + fSum += fVal; + values.push_back(fVal); + fCount++; + } + } + } + } + break; + default : + SetError(FormulaError::IllegalParameter); + break; + } + } + + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return false; + } // if (nGlobalError != FormulaError::NONE) + return true; +} + +void ScInterpreter::CalculateSkewOrSkewp( bool bSkewp ) +{ + KahanSum fSum; + double fCount; + std::vector<double> values; + if (!CalculateSkew( fSum, fCount, values)) + return; + // SKEW/SKEWP's constraints: they require at least three numbers + if (fCount < 3.0) + { + // for interoperability with Excel + PushError(FormulaError::DivisionByZero); + return; + } + + KahanSum vSum; + double fMean = fSum.get() / fCount; + for (double v : values) + vSum += (v - fMean) * (v - fMean); + + double fStdDev = sqrt( vSum.get() / (bSkewp ? fCount : (fCount - 1.0))); + if (fStdDev == 0) + { + PushIllegalArgument(); + return; + } + + KahanSum xcube = 0.0; + for (double v : values) + { + double dx = (v - fMean) / fStdDev; + xcube += dx * dx * dx; + } + + if (bSkewp) + PushDouble( xcube.get() / fCount ); + else + PushDouble( ((xcube.get() * fCount) / (fCount - 1.0)) / (fCount - 2.0) ); +} + +void ScInterpreter::ScSkew() +{ + CalculateSkewOrSkewp( false ); +} + +void ScInterpreter::ScSkewp() +{ + CalculateSkewOrSkewp( true ); +} + +double ScInterpreter::GetMedian( vector<double> & rArray ) +{ + size_t nSize = rArray.size(); + if (nSize == 0 || nGlobalError != FormulaError::NONE) + { + SetError( FormulaError::NoValue); + return 0.0; + } + + // Upper median. + size_t nMid = nSize / 2; + vector<double>::iterator iMid = rArray.begin() + nMid; + ::std::nth_element( rArray.begin(), iMid, rArray.end()); + if (nSize & 1) + return *iMid; // Lower and upper median are equal. + else + { + double fUp = *iMid; + // Lower median. + iMid = ::std::max_element( rArray.begin(), rArray.begin() + nMid); + return (fUp + *iMid) / 2; + } +} + +void ScInterpreter::ScMedian() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCountMin( nParamCount, 1 ) ) + return; + vector<double> aArray; + GetNumberSequenceArray( nParamCount, aArray, false ); + PushDouble( GetMedian( aArray)); +} + +double ScInterpreter::GetPercentile( vector<double> & rArray, double fPercentile ) +{ + size_t nSize = rArray.size(); + if (nSize == 1) + return rArray[0]; + else + { + size_t nIndex = static_cast<size_t>(::rtl::math::approxFloor( fPercentile * (nSize-1))); + double fDiff = fPercentile * (nSize-1) - ::rtl::math::approxFloor( fPercentile * (nSize-1)); + OSL_ENSURE(nIndex < nSize, "GetPercentile: wrong index(1)"); + vector<double>::iterator iter = rArray.begin() + nIndex; + ::std::nth_element( rArray.begin(), iter, rArray.end()); + if (fDiff <= 0.0) + { + // Note: neg fDiff seen with forum-mso-en4-719754.xlsx with + // fPercentile of near 1 where approxFloor gave nIndex of nSize-1 + // resulting in a non-zero tiny negative fDiff. + return *iter; + } + else + { + OSL_ENSURE(nIndex < nSize-1, "GetPercentile: wrong index(2)"); + double fVal = *iter; + iter = ::std::min_element( rArray.begin() + nIndex + 1, rArray.end()); + return fVal + fDiff * (*iter - fVal); + } + } +} + +double ScInterpreter::GetPercentileExclusive( vector<double> & rArray, double fPercentile ) +{ + size_t nSize1 = rArray.size() + 1; + if ( rArray.empty() || nSize1 == 1 || nGlobalError != FormulaError::NONE) + { + SetError( FormulaError::NoValue ); + return 0.0; + } + if ( fPercentile * nSize1 < 1.0 || fPercentile * nSize1 > static_cast<double>( nSize1 - 1 ) ) + { + SetError( FormulaError::IllegalParameter ); + return 0.0; + } + + size_t nIndex = static_cast<size_t>(::rtl::math::approxFloor( fPercentile * nSize1 - 1 )); + double fDiff = fPercentile * nSize1 - 1 - ::rtl::math::approxFloor( fPercentile * nSize1 - 1 ); + OSL_ENSURE(nIndex < ( nSize1 - 1 ), "GetPercentile: wrong index(1)"); + vector<double>::iterator iter = rArray.begin() + nIndex; + ::std::nth_element( rArray.begin(), iter, rArray.end()); + if (fDiff == 0.0) + return *iter; + else + { + OSL_ENSURE(nIndex < nSize1, "GetPercentile: wrong index(2)"); + double fVal = *iter; + iter = ::std::min_element( rArray.begin() + nIndex + 1, rArray.end()); + return fVal + fDiff * (*iter - fVal); + } +} + +void ScInterpreter::ScPercentile( bool bInclusive ) +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + double alpha = GetDouble(); + if ( bInclusive ? ( alpha < 0.0 || alpha > 1.0 ) : ( alpha <= 0.0 || alpha >= 1.0 ) ) + { + PushIllegalArgument(); + return; + } + vector<double> aArray; + GetNumberSequenceArray( 1, aArray, false ); + if ( aArray.empty() || nGlobalError != FormulaError::NONE ) + { + PushNoValue(); + return; + } + if ( bInclusive ) + PushDouble( GetPercentile( aArray, alpha )); + else + PushDouble( GetPercentileExclusive( aArray, alpha )); +} + +void ScInterpreter::ScQuartile( bool bInclusive ) +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + double fFlag = ::rtl::math::approxFloor(GetDouble()); + if ( bInclusive ? ( fFlag < 0.0 || fFlag > 4.0 ) : ( fFlag <= 0.0 || fFlag >= 4.0 ) ) + { + PushIllegalArgument(); + return; + } + vector<double> aArray; + GetNumberSequenceArray( 1, aArray, false ); + if ( aArray.empty() || nGlobalError != FormulaError::NONE ) + { + PushNoValue(); + return; + } + if ( bInclusive ) + PushDouble( fFlag == 2.0 ? GetMedian( aArray ) : GetPercentile( aArray, 0.25 * fFlag ) ); + else + PushDouble( fFlag == 2.0 ? GetMedian( aArray ) : GetPercentileExclusive( aArray, 0.25 * fFlag ) ); +} + +void ScInterpreter::ScModalValue() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCountMin( nParamCount, 1 ) ) + return; + vector<double> aSortArray; + GetSortArray( nParamCount, aSortArray, nullptr, false, false ); + SCSIZE nSize = aSortArray.size(); + if (nSize == 0 || nGlobalError != FormulaError::NONE) + PushNoValue(); + else + { + SCSIZE nMaxIndex = 0, nMax = 1, nCount = 1; + double nOldVal = aSortArray[0]; + SCSIZE i; + for ( i = 1; i < nSize; i++) + { + if (aSortArray[i] == nOldVal) + nCount++; + else + { + nOldVal = aSortArray[i]; + if (nCount > nMax) + { + nMax = nCount; + nMaxIndex = i-1; + } + nCount = 1; + } + } + if (nCount > nMax) + { + nMax = nCount; + nMaxIndex = i-1; + } + if (nMax == 1 && nCount == 1) + PushNoValue(); + else if (nMax == 1) + PushDouble(nOldVal); + else + PushDouble(aSortArray[nMaxIndex]); + } +} + +void ScInterpreter::ScModalValue_MS( bool bSingle ) +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCountMin( nParamCount, 1 ) ) + return; + vector<double> aArray; + GetNumberSequenceArray( nParamCount, aArray, false ); + vector< double > aSortArray( aArray ); + QuickSort( aSortArray, nullptr ); + SCSIZE nSize = aSortArray.size(); + if ( nSize == 0 || nGlobalError != FormulaError::NONE ) + PushNoValue(); + else + { + SCSIZE nMax = 1, nCount = 1; + double nOldVal = aSortArray[ 0 ]; + vector< double > aResultArray( 1 ); + SCSIZE i; + for ( i = 1; i < nSize; i++ ) + { + if ( aSortArray[ i ] == nOldVal ) + nCount++; + else + { + if ( nCount >= nMax && nCount > 1 ) + { + if ( nCount > nMax ) + { + nMax = nCount; + if ( aResultArray.size() != 1 ) + vector< double >( 1 ).swap( aResultArray ); + aResultArray[ 0 ] = nOldVal; + } + else + aResultArray.emplace_back( nOldVal ); + } + nOldVal = aSortArray[ i ]; + nCount = 1; + } + } + if ( nCount >= nMax && nCount > 1 ) + { + if ( nCount > nMax ) + vector< double >().swap( aResultArray ); + aResultArray.emplace_back( nOldVal ); + } + if ( nMax == 1 && nCount == 1 ) + PushNoValue(); + else if ( nMax == 1 ) + PushDouble( nOldVal ); // there is only 1 result, no reordering needed + else + { + // sort resultArray according to ordering of aArray + vector< vector< double > > aOrder; + aOrder.resize( aResultArray.size(), vector< double >( 2 ) ); + for ( i = 0; i < aResultArray.size(); i++ ) + { + for ( SCSIZE j = 0; j < nSize; j++ ) + { + if ( aArray[ j ] == aResultArray[ i ] ) + { + aOrder[ i ][ 0 ] = aResultArray[ i ]; + aOrder[ i ][ 1 ] = j; + break; + } + } + } + sort( aOrder.begin(), aOrder.end(), []( const std::vector< double >& lhs, + const std::vector< double >& rhs ) + { return lhs[ 1 ] < rhs[ 1 ]; } ); + + if ( bSingle ) + PushDouble( aOrder[ 0 ][ 0 ] ); + else + { + // put result in correct order in aResultArray + for ( i = 0; i < aResultArray.size(); i++ ) + aResultArray[ i ] = aOrder[ i ][ 0 ]; + ScMatrixRef pResMatrix = GetNewMat( 1, aResultArray.size(), true ); + pResMatrix->PutDoubleVector( aResultArray, 0, 0 ); + PushMatrix( pResMatrix ); + } + } + } +} + +void ScInterpreter::CalculateSmallLarge(bool bSmall) +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + SCSIZE nCol = 0, nRow = 0; + const auto aArray = GetRankNumberArray(nCol, nRow); + const size_t nRankArraySize = aArray.size(); + if (nRankArraySize == 0 || nGlobalError != FormulaError::NONE) + { + PushNoValue(); + return; + } + assert(nRankArraySize == nCol * nRow); + + std::vector<SCSIZE> aRankArray; + aRankArray.reserve(nRankArraySize); + std::transform(aArray.begin(), aArray.end(), std::back_inserter(aRankArray), + [bSmall](double f) { + f = (bSmall ? rtl::math::approxFloor(f) : rtl::math::approxCeil(f)); + // Valid ranks are >= 1. + if (f < 1.0 || !o3tl::convertsToAtMost(f, std::numeric_limits<SCSIZE>::max())) + return static_cast<SCSIZE>(0); + return static_cast<SCSIZE>(f); + }); + + vector<double> aSortArray; + GetNumberSequenceArray(1, aSortArray, false ); + const SCSIZE nSize = aSortArray.size(); + if (nSize == 0 || nGlobalError != FormulaError::NONE) + PushNoValue(); + else if (nRankArraySize == 1) + { + const SCSIZE k = aRankArray[0]; + if (k < 1 || nSize < k) + { + if (!std::isfinite(aArray[0])) + PushDouble(aArray[0]); // propagates error + else + PushNoValue(); + } + else + { + vector<double>::iterator iPos = aSortArray.begin() + (bSmall ? k-1 : nSize-k); + ::std::nth_element( aSortArray.begin(), iPos, aSortArray.end()); + PushDouble( *iPos); + } + } + else + { + std::set<SCSIZE> aIndices; + for (SCSIZE n : aRankArray) + { + if (1 <= n && n <= nSize) + aIndices.insert(bSmall ? n-1 : nSize-n); + } + // We can spare sorting when the total number of ranks is small enough. + // Find only the elements at given indices if, arbitrarily, the index size is + // smaller than 1/3 of the haystack array's size; just sort it squarely, otherwise. + if (aIndices.size() < nSize/3) + { + auto itBegin = aSortArray.begin(); + for (SCSIZE i : aIndices) + { + auto it = aSortArray.begin() + i; + std::nth_element(itBegin, it, aSortArray.end()); + itBegin = ++it; + } + } + else + std::sort(aSortArray.begin(), aSortArray.end()); + + std::vector<double> aResultArray; + aResultArray.reserve(nRankArraySize); + for (size_t i = 0; i < nRankArraySize; ++i) + { + const SCSIZE n = aRankArray[i]; + if (1 <= n && n <= nSize) + aResultArray.push_back( aSortArray[bSmall ? n-1 : nSize-n]); + else if (!std::isfinite( aArray[i])) + aResultArray.push_back( aArray[i]); // propagate error + else + aResultArray.push_back( CreateDoubleError( FormulaError::IllegalArgument)); + } + ScMatrixRef pResult = GetNewMat(nCol, nRow, aResultArray); + PushMatrix(pResult); + } +} + +void ScInterpreter::ScLarge() +{ + CalculateSmallLarge(false); +} + +void ScInterpreter::ScSmall() +{ + CalculateSmallLarge(true); +} + +void ScInterpreter::ScPercentrank( bool bInclusive ) +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2, 3 ) ) + return; + double fSignificance = ( nParamCount == 3 ? ::rtl::math::approxFloor( GetDouble() ) : 3.0 ); + if ( fSignificance < 1.0 ) + { + PushIllegalArgument(); + return; + } + double fNum = GetDouble(); + vector<double> aSortArray; + GetSortArray( 1, aSortArray, nullptr, false, false ); + SCSIZE nSize = aSortArray.size(); + if ( nSize == 0 || nGlobalError != FormulaError::NONE ) + PushNoValue(); + else + { + if ( fNum < aSortArray[ 0 ] || fNum > aSortArray[ nSize - 1 ] ) + PushNoValue(); + else + { + double fRes; + if ( nSize == 1 ) + fRes = 1.0; // fNum == aSortArray[ 0 ], see test above + else + fRes = GetPercentrank( aSortArray, fNum, bInclusive ); + if ( fRes != 0.0 ) + { + double fExp = ::rtl::math::approxFloor( log10( fRes ) ) + 1.0 - fSignificance; + fRes = ::rtl::math::round( fRes * pow( 10, -fExp ) ) / pow( 10, -fExp ); + } + PushDouble( fRes ); + } + } +} + +double ScInterpreter::GetPercentrank( ::std::vector<double> & rArray, double fVal, bool bInclusive ) +{ + SCSIZE nSize = rArray.size(); + double fRes; + if ( fVal == rArray[ 0 ] ) + { + if ( bInclusive ) + fRes = 0.0; + else + fRes = 1.0 / static_cast<double>( nSize + 1 ); + } + else + { + SCSIZE nOldCount = 0; + double fOldVal = rArray[ 0 ]; + SCSIZE i; + for ( i = 1; i < nSize && rArray[ i ] < fVal; i++ ) + { + if ( rArray[ i ] != fOldVal ) + { + nOldCount = i; + fOldVal = rArray[ i ]; + } + } + if ( rArray[ i ] != fOldVal ) + nOldCount = i; + if ( fVal == rArray[ i ] ) + { + if ( bInclusive ) + fRes = div( nOldCount, nSize - 1 ); + else + fRes = static_cast<double>( i + 1 ) / static_cast<double>( nSize + 1 ); + } + else + { + // nOldCount is the count of smaller entries + // fVal is between rArray[ nOldCount - 1 ] and rArray[ nOldCount ] + // use linear interpolation to find a position between the entries + if ( nOldCount == 0 ) + { + OSL_FAIL( "should not happen" ); + fRes = 0.0; + } + else + { + double fFract = ( fVal - rArray[ nOldCount - 1 ] ) / + ( rArray[ nOldCount ] - rArray[ nOldCount - 1 ] ); + if ( bInclusive ) + fRes = div( static_cast<double>( nOldCount - 1 ) + fFract, nSize - 1 ); + else + fRes = ( static_cast<double>(nOldCount) + fFract ) / static_cast<double>( nSize + 1 ); + } + } + } + return fRes; +} + +void ScInterpreter::ScTrimMean() +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + double alpha = GetDouble(); + if (alpha < 0.0 || alpha >= 1.0) + { + PushIllegalArgument(); + return; + } + vector<double> aSortArray; + GetSortArray( 1, aSortArray, nullptr, false, false ); + SCSIZE nSize = aSortArray.size(); + if (nSize == 0 || nGlobalError != FormulaError::NONE) + PushNoValue(); + else + { + sal_uLong nIndex = static_cast<sal_uLong>(::rtl::math::approxFloor(alpha*static_cast<double>(nSize))); + if (nIndex % 2 != 0) + nIndex--; + nIndex /= 2; + OSL_ENSURE(nIndex < nSize, "ScTrimMean: wrong index"); + KahanSum fSum = 0.0; + for (SCSIZE i = nIndex; i < nSize-nIndex; i++) + fSum += aSortArray[i]; + PushDouble(fSum.get()/static_cast<double>(nSize-2*nIndex)); + } +} + +std::vector<double> ScInterpreter::GetRankNumberArray( SCSIZE& rCol, SCSIZE& rRow ) +{ + std::vector<double> aArray; + switch (GetStackType()) + { + case svDouble: + aArray.push_back(PopDouble()); + rCol = rRow = 1; + break; + case svSingleRef: + { + ScAddress aAdr; + PopSingleRef(aAdr); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + { + aArray.push_back(GetCellValue(aAdr, aCell)); + rCol = rRow = 1; + } + } + break; + case svDoubleRef: + { + ScRange aRange; + PopDoubleRef(aRange, true); + if (nGlobalError != FormulaError::NONE) + break; + + // give up unless the start and end are in the same sheet + if (aRange.aStart.Tab() != aRange.aEnd.Tab()) + { + SetError(FormulaError::IllegalParameter); + break; + } + + // the range already is in order + assert(aRange.aStart.Col() <= aRange.aEnd.Col()); + assert(aRange.aStart.Row() <= aRange.aEnd.Row()); + rCol = aRange.aEnd.Col() - aRange.aStart.Col() + 1; + rRow = aRange.aEnd.Row() - aRange.aStart.Row() + 1; + aArray.reserve(rCol * rRow); + + FormulaError nErr = FormulaError::NONE; + double fCellVal; + ScValueIterator aValIter(mrContext, aRange, mnSubTotalFlags); + if (aValIter.GetFirst(fCellVal, nErr)) + { + do + aArray.push_back(fCellVal); + while (aValIter.GetNext(fCellVal, nErr) && nErr == FormulaError::NONE); + } + // Note that SMALL() and LARGE() rank parameters (2nd) have + // ParamClass::Value, so in array mode this is never hit and + // argument was converted to matrix instead, but for normal + // evaluation any non-numeric value including empty cell will + // result in error anyway, so just clear and propagate an existing + // error here already. + if (aArray.size() != rCol * rRow) + { + aArray.clear(); + SetError(nErr); + } + } + break; + case svMatrix: + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatrixRef pMat = GetMatrix(); + if (!pMat) + break; + + const SCSIZE nCount = pMat->GetElementCount(); + aArray.reserve(nCount); + // Do not propagate errors from matrix elements as global error. + pMat->SetErrorInterpreter(nullptr); + if (pMat->IsNumeric()) + { + for (SCSIZE i = 0; i < nCount; ++i) + aArray.push_back(pMat->GetDouble(i)); + } + else + { + for (SCSIZE i = 0; i < nCount; ++i) + { + if (pMat->IsValue(i)) + aArray.push_back( pMat->GetDouble(i)); + else + aArray.push_back( CreateDoubleError( FormulaError::NoValue)); + } + } + pMat->GetDimensions(rCol, rRow); + } + break; + default: + PopError(); + SetError(FormulaError::IllegalParameter); + break; + } + return aArray; +} + +void ScInterpreter::GetNumberSequenceArray( sal_uInt8 nParamCount, vector<double>& rArray, bool bConvertTextInArray ) +{ + ScAddress aAdr; + ScRange aRange; + const bool bIgnoreErrVal = bool(mnSubTotalFlags & SubtotalFlags::IgnoreErrVal); + short nParam = nParamCount; + size_t nRefInList = 0; + ReverseStack( nParamCount ); + while (nParam-- > 0) + { + const StackVar eStackType = GetStackType(); + switch (eStackType) + { + case svDouble : + rArray.push_back( PopDouble()); + break; + case svSingleRef : + { + PopSingleRef( aAdr ); + ScRefCellValue aCell(mrDoc, aAdr); + if (bIgnoreErrVal && aCell.hasError()) + ; // nothing + else if (aCell.hasNumeric()) + rArray.push_back(GetCellValue(aAdr, aCell)); + } + break; + case svDoubleRef : + case svRefList : + { + PopDoubleRef( aRange, nParam, nRefInList); + if (nGlobalError != FormulaError::NONE) + break; + + aRange.PutInOrder(); + SCSIZE nCellCount = aRange.aEnd.Col() - aRange.aStart.Col() + 1; + nCellCount *= aRange.aEnd.Row() - aRange.aStart.Row() + 1; + rArray.reserve( rArray.size() + nCellCount); + + FormulaError nErr = FormulaError::NONE; + double fCellVal; + ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags ); + if (aValIter.GetFirst( fCellVal, nErr)) + { + if (bIgnoreErrVal) + { + if (nErr == FormulaError::NONE) + rArray.push_back( fCellVal); + while (aValIter.GetNext( fCellVal, nErr)) + { + if (nErr == FormulaError::NONE) + rArray.push_back( fCellVal); + } + } + else + { + rArray.push_back( fCellVal); + SetError(nErr); + while ((nErr == FormulaError::NONE) && aValIter.GetNext( fCellVal, nErr)) + rArray.push_back( fCellVal); + SetError(nErr); + } + } + } + break; + case svMatrix : + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatrixRef pMat = GetMatrix(); + if (!pMat) + break; + + SCSIZE nCount = pMat->GetElementCount(); + rArray.reserve( rArray.size() + nCount); + if (pMat->IsNumeric()) + { + if (bIgnoreErrVal) + { + for (SCSIZE i = 0; i < nCount; ++i) + { + const double fVal = pMat->GetDouble(i); + if (nGlobalError == FormulaError::NONE) + rArray.push_back( fVal); + else + nGlobalError = FormulaError::NONE; + } + } + else + { + for (SCSIZE i = 0; i < nCount; ++i) + rArray.push_back( pMat->GetDouble(i)); + } + } + else if (bConvertTextInArray && eStackType == svMatrix) + { + for (SCSIZE i = 0; i < nCount; ++i) + { + if ( pMat->IsValue( i ) ) + { + if (bIgnoreErrVal) + { + const double fVal = pMat->GetDouble(i); + if (nGlobalError == FormulaError::NONE) + rArray.push_back( fVal); + else + nGlobalError = FormulaError::NONE; + } + else + rArray.push_back( pMat->GetDouble(i)); + } + else + { + // tdf#88547 try to convert string to (date)value + OUString aStr = pMat->GetString( i ).getString(); + if ( aStr.getLength() > 0 ) + { + FormulaError nErr = nGlobalError; + nGlobalError = FormulaError::NONE; + double fVal = ConvertStringToValue( aStr ); + if ( nGlobalError == FormulaError::NONE ) + { + rArray.push_back( fVal ); + nGlobalError = nErr; + } + else + { + if (!bIgnoreErrVal) + rArray.push_back( CreateDoubleError( FormulaError::NoValue)); + // Propagate previous error if any, else + // the current #VALUE! error, unless + // ignoring error values. + if (nErr != FormulaError::NONE) + nGlobalError = nErr; + else if (!bIgnoreErrVal) + nGlobalError = FormulaError::NoValue; + else + nGlobalError = FormulaError::NONE; + } + } + } + } + } + else + { + if (bIgnoreErrVal) + { + for (SCSIZE i = 0; i < nCount; ++i) + { + if (pMat->IsValue(i)) + { + const double fVal = pMat->GetDouble(i); + if (nGlobalError == FormulaError::NONE) + rArray.push_back( fVal); + else + nGlobalError = FormulaError::NONE; + } + } + } + else + { + for (SCSIZE i = 0; i < nCount; ++i) + { + if (pMat->IsValue(i)) + rArray.push_back( pMat->GetDouble(i)); + } + } + } + } + break; + default : + PopError(); + SetError( FormulaError::IllegalParameter); + break; + } + if (nGlobalError != FormulaError::NONE) + break; // while + } + // nParam > 0 in case of error, clean stack environment and obtain earlier + // error if there was one. + while (nParam-- > 0) + PopError(); +} + +void ScInterpreter::GetSortArray( sal_uInt8 nParamCount, vector<double>& rSortArray, vector<tools::Long>* pIndexOrder, bool bConvertTextInArray, bool bAllowEmptyArray ) +{ + GetNumberSequenceArray( nParamCount, rSortArray, bConvertTextInArray ); + if (rSortArray.size() > MAX_COUNT_DOUBLE_FOR_SORT(mrDoc.GetSheetLimits())) + SetError( FormulaError::MatrixSize); + else if ( rSortArray.empty() ) + { + if ( bAllowEmptyArray ) + return; + SetError( FormulaError::NoValue); + } + + if (nGlobalError == FormulaError::NONE) + QuickSort( rSortArray, pIndexOrder); +} + +static void lcl_QuickSort( tools::Long nLo, tools::Long nHi, vector<double>& rSortArray, vector<tools::Long>* pIndexOrder ) +{ + // If pIndexOrder is not NULL, we assume rSortArray.size() == pIndexOrder->size(). + + using ::std::swap; + + if (nHi - nLo == 1) + { + if (rSortArray[nLo] > rSortArray[nHi]) + { + swap(rSortArray[nLo], rSortArray[nHi]); + if (pIndexOrder) + swap(pIndexOrder->at(nLo), pIndexOrder->at(nHi)); + } + return; + } + + tools::Long ni = nLo; + tools::Long nj = nHi; + do + { + double fLo = rSortArray[nLo]; + while (ni <= nHi && rSortArray[ni] < fLo) ni++; + while (nj >= nLo && fLo < rSortArray[nj]) nj--; + if (ni <= nj) + { + if (ni != nj) + { + swap(rSortArray[ni], rSortArray[nj]); + if (pIndexOrder) + swap(pIndexOrder->at(ni), pIndexOrder->at(nj)); + } + + ++ni; + --nj; + } + } + while (ni < nj); + + if ((nj - nLo) < (nHi - ni)) + { + if (nLo < nj) lcl_QuickSort(nLo, nj, rSortArray, pIndexOrder); + if (ni < nHi) lcl_QuickSort(ni, nHi, rSortArray, pIndexOrder); + } + else + { + if (ni < nHi) lcl_QuickSort(ni, nHi, rSortArray, pIndexOrder); + if (nLo < nj) lcl_QuickSort(nLo, nj, rSortArray, pIndexOrder); + } +} + +void ScInterpreter::QuickSort( vector<double>& rSortArray, vector<tools::Long>* pIndexOrder ) +{ + tools::Long n = static_cast<tools::Long>(rSortArray.size()); + + if (pIndexOrder) + { + pIndexOrder->clear(); + pIndexOrder->reserve(n); + for (tools::Long i = 0; i < n; ++i) + pIndexOrder->push_back(i); + } + + if (n < 2) + return; + + size_t nValCount = rSortArray.size(); + for (size_t i = 0; (i + 4) <= nValCount-1; i += 4) + { + size_t nInd = comphelper::rng::uniform_size_distribution(0, nValCount-2); + ::std::swap( rSortArray[i], rSortArray[nInd]); + if (pIndexOrder) + ::std::swap( pIndexOrder->at(i), pIndexOrder->at(nInd)); + } + + lcl_QuickSort(0, n-1, rSortArray, pIndexOrder); +} + +void ScInterpreter::ScRank( bool bAverage ) +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2, 3 ) ) + return; + bool bAscending; + if ( nParamCount == 3 ) + bAscending = GetBool(); + else + bAscending = false; + + vector<double> aSortArray; + GetSortArray( 1, aSortArray, nullptr, false, false ); + double fVal = GetDouble(); + SCSIZE nSize = aSortArray.size(); + if ( nSize == 0 || nGlobalError != FormulaError::NONE ) + PushNoValue(); + else + { + if ( fVal < aSortArray[ 0 ] || fVal > aSortArray[ nSize - 1 ] ) + PushError( FormulaError::NotAvailable); + else + { + double fLastPos = 0; + double fFirstPos = -1.0; + bool bFinished = false; + SCSIZE i; + for (i = 0; i < nSize && !bFinished; i++) + { + if ( aSortArray[ i ] == fVal ) + { + if ( fFirstPos < 0 ) + fFirstPos = i + 1.0; + } + else + { + if ( aSortArray[ i ] > fVal ) + { + fLastPos = i; + bFinished = true; + } + } + } + if ( !bFinished ) + fLastPos = i; + if (fFirstPos <= 0) + { + PushError( FormulaError::NotAvailable); + } + else if ( !bAverage ) + { + if ( bAscending ) + PushDouble( fFirstPos ); + else + PushDouble( nSize + 1.0 - fLastPos ); + } + else + { + if ( bAscending ) + PushDouble( ( fFirstPos + fLastPos ) / 2.0 ); + else + PushDouble( nSize + 1.0 - ( fFirstPos + fLastPos ) / 2.0 ); + } + } + } +} + +void ScInterpreter::ScAveDev() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCountMin( nParamCount, 1 ) ) + return; + sal_uInt16 SaveSP = sp; + double nMiddle = 0.0; + KahanSum rVal = 0.0; + double rValCount = 0.0; + ScAddress aAdr; + ScRange aRange; + short nParam = nParamCount; + size_t nRefInList = 0; + while (nParam-- > 0) + { + switch (GetStackType()) + { + case svDouble : + rVal += GetDouble(); + rValCount++; + break; + case svSingleRef : + { + PopSingleRef( aAdr ); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + { + rVal += GetCellValue(aAdr, aCell); + rValCount++; + } + } + break; + case svDoubleRef : + case svRefList : + { + FormulaError nErr = FormulaError::NONE; + double nCellVal; + PopDoubleRef( aRange, nParam, nRefInList); + ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags ); + if (aValIter.GetFirst(nCellVal, nErr)) + { + rVal += nCellVal; + rValCount++; + SetError(nErr); + while ((nErr == FormulaError::NONE) && aValIter.GetNext(nCellVal, nErr)) + { + rVal += nCellVal; + rValCount++; + } + SetError(nErr); + } + } + break; + case svMatrix : + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatrixRef pMat = GetMatrix(); + if (pMat) + { + SCSIZE nCount = pMat->GetElementCount(); + if (pMat->IsNumeric()) + { + for (SCSIZE nElem = 0; nElem < nCount; nElem++) + { + rVal += pMat->GetDouble(nElem); + rValCount++; + } + } + else + { + for (SCSIZE nElem = 0; nElem < nCount; nElem++) + if (!pMat->IsStringOrEmpty(nElem)) + { + rVal += pMat->GetDouble(nElem); + rValCount++; + } + } + } + } + break; + default : + SetError(FormulaError::IllegalParameter); + break; + } + } + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + nMiddle = rVal.get() / rValCount; + sp = SaveSP; + rVal = 0.0; + nParam = nParamCount; + nRefInList = 0; + while (nParam-- > 0) + { + switch (GetStackType()) + { + case svDouble : + rVal += std::abs(GetDouble() - nMiddle); + break; + case svSingleRef : + { + PopSingleRef( aAdr ); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + rVal += std::abs(GetCellValue(aAdr, aCell) - nMiddle); + } + break; + case svDoubleRef : + case svRefList : + { + FormulaError nErr = FormulaError::NONE; + double nCellVal; + PopDoubleRef( aRange, nParam, nRefInList); + ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags ); + if (aValIter.GetFirst(nCellVal, nErr)) + { + rVal += std::abs(nCellVal - nMiddle); + while (aValIter.GetNext(nCellVal, nErr)) + rVal += std::abs(nCellVal - nMiddle); + } + } + break; + case svMatrix : + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatrixRef pMat = GetMatrix(); + if (pMat) + { + SCSIZE nCount = pMat->GetElementCount(); + if (pMat->IsNumeric()) + { + for (SCSIZE nElem = 0; nElem < nCount; nElem++) + { + rVal += std::abs(pMat->GetDouble(nElem) - nMiddle); + } + } + else + { + for (SCSIZE nElem = 0; nElem < nCount; nElem++) + { + if (!pMat->IsStringOrEmpty(nElem)) + rVal += std::abs(pMat->GetDouble(nElem) - nMiddle); + } + } + } + } + break; + default : SetError(FormulaError::IllegalParameter); break; + } + } + PushDouble(rVal.get() / rValCount); +} + +void ScInterpreter::ScDevSq() +{ + auto VarResult = []( double fVal, size_t /*nValCount*/ ) + { + return fVal; + }; + GetStVarParams( false /*bTextAsZero*/, VarResult); +} + +void ScInterpreter::ScProbability() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 3, 4 ) ) + return; + double fUp, fLo; + fUp = GetDouble(); + if (nParamCount == 4) + fLo = GetDouble(); + else + fLo = fUp; + if (fLo > fUp) + std::swap( fLo, fUp ); + ScMatrixRef pMatP = GetMatrix(); + ScMatrixRef pMatW = GetMatrix(); + if (!pMatP || !pMatW) + PushIllegalParameter(); + else + { + SCSIZE nC1, nC2; + SCSIZE nR1, nR2; + pMatP->GetDimensions(nC1, nR1); + pMatW->GetDimensions(nC2, nR2); + if (nC1 != nC2 || nR1 != nR2 || nC1 == 0 || nR1 == 0 || + nC2 == 0 || nR2 == 0) + PushNA(); + else + { + KahanSum fSum = 0.0; + KahanSum fRes = 0.0; + bool bStop = false; + double fP, fW; + for ( SCSIZE i = 0; i < nC1 && !bStop; i++ ) + { + for (SCSIZE j = 0; j < nR1 && !bStop; ++j ) + { + if (pMatP->IsValue(i,j) && pMatW->IsValue(i,j)) + { + fP = pMatP->GetDouble(i,j); + fW = pMatW->GetDouble(i,j); + if (fP < 0.0 || fP > 1.0) + bStop = true; + else + { + fSum += fP; + if (fW >= fLo && fW <= fUp) + fRes += fP; + } + } + else + SetError( FormulaError::IllegalArgument); + } + } + if (bStop || std::abs((fSum -1.0).get()) > 1.0E-7) + PushNoValue(); + else + PushDouble(fRes.get()); + } + } +} + +void ScInterpreter::ScCorrel() +{ + // This is identical to ScPearson() + ScPearson(); +} + +void ScInterpreter::ScCovarianceP() +{ + CalculatePearsonCovar( false, false, false ); +} + +void ScInterpreter::ScCovarianceS() +{ + CalculatePearsonCovar( false, false, true ); +} + +void ScInterpreter::ScPearson() +{ + CalculatePearsonCovar( true, false, false ); +} + +void ScInterpreter::CalculatePearsonCovar( bool _bPearson, bool _bStexy, bool _bSample ) +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + ScMatrixRef pMat1 = GetMatrix(); + ScMatrixRef pMat2 = GetMatrix(); + if (!pMat1 || !pMat2) + { + PushIllegalParameter(); + return; + } + SCSIZE nC1, nC2; + SCSIZE nR1, nR2; + pMat1->GetDimensions(nC1, nR1); + pMat2->GetDimensions(nC2, nR2); + if (nR1 != nR2 || nC1 != nC2) + { + PushIllegalArgument(); + return; + } + /* #i78250# + * (sum((X-MeanX)(Y-MeanY)))/N equals (SumXY)/N-MeanX*MeanY mathematically, + * but the latter produces wrong results if the absolute values are high, + * for example above 10^8 + */ + double fCount = 0.0; + KahanSum fSumX = 0.0; + KahanSum fSumY = 0.0; + + for (SCSIZE i = 0; i < nC1; i++) + { + for (SCSIZE j = 0; j < nR1; j++) + { + if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j)) + { + fSumX += pMat1->GetDouble(i,j); + fSumY += pMat2->GetDouble(i,j); + fCount++; + } + } + } + if (fCount < (_bStexy ? 3.0 : (_bSample ? 2.0 : 1.0))) + PushNoValue(); + else + { + KahanSum fSumDeltaXDeltaY = 0.0; // sum of (ValX-MeanX)*(ValY-MeanY) + KahanSum fSumSqrDeltaX = 0.0; // sum of (ValX-MeanX)^2 + KahanSum fSumSqrDeltaY = 0.0; // sum of (ValY-MeanY)^2 + const double fMeanX = fSumX.get() / fCount; + const double fMeanY = fSumY.get() / fCount; + for (SCSIZE i = 0; i < nC1; i++) + { + for (SCSIZE j = 0; j < nR1; j++) + { + if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j)) + { + const double fValX = pMat1->GetDouble(i,j); + const double fValY = pMat2->GetDouble(i,j); + fSumDeltaXDeltaY += (fValX - fMeanX) * (fValY - fMeanY); + if ( _bPearson ) + { + fSumSqrDeltaX += (fValX - fMeanX) * (fValX - fMeanX); + fSumSqrDeltaY += (fValY - fMeanY) * (fValY - fMeanY); + } + } + } + } + if ( _bPearson ) + { + // tdf#94962 - Values below the numerical limit lead to unexpected results + if (fSumSqrDeltaX < ::std::numeric_limits<double>::min() + || (!_bStexy && fSumSqrDeltaY < ::std::numeric_limits<double>::min())) + PushError( FormulaError::DivisionByZero); + else if ( _bStexy ) + PushDouble( sqrt( ( fSumSqrDeltaY - fSumDeltaXDeltaY * + fSumDeltaXDeltaY / fSumSqrDeltaX ).get() / (fCount-2))); + else + PushDouble( fSumDeltaXDeltaY.get() / sqrt( fSumSqrDeltaX.get() * fSumSqrDeltaY.get() )); + } + else + { + if ( _bSample ) + PushDouble( fSumDeltaXDeltaY.get() / ( fCount - 1 ) ); + else + PushDouble( fSumDeltaXDeltaY.get() / fCount); + } + } +} + +void ScInterpreter::ScRSQ() +{ + // Same as ScPearson()*ScPearson() + ScPearson(); + if (nGlobalError != FormulaError::NONE) + return; + + switch (GetStackType()) + { + case svDouble: + { + double fVal = PopDouble(); + PushDouble( fVal * fVal); + } + break; + default: + PopError(); + PushNoValue(); + } +} + +void ScInterpreter::ScSTEYX() +{ + CalculatePearsonCovar( true, true, false ); +} +void ScInterpreter::CalculateSlopeIntercept(bool bSlope) +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + ScMatrixRef pMat1 = GetMatrix(); + ScMatrixRef pMat2 = GetMatrix(); + if (!pMat1 || !pMat2) + { + PushIllegalParameter(); + return; + } + SCSIZE nC1, nC2; + SCSIZE nR1, nR2; + pMat1->GetDimensions(nC1, nR1); + pMat2->GetDimensions(nC2, nR2); + if (nR1 != nR2 || nC1 != nC2) + { + PushIllegalArgument(); + return; + } + // #i78250# numerical stability improved + double fCount = 0.0; + KahanSum fSumX = 0.0; + KahanSum fSumY = 0.0; + + for (SCSIZE i = 0; i < nC1; i++) + { + for (SCSIZE j = 0; j < nR1; j++) + { + if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j)) + { + fSumX += pMat1->GetDouble(i,j); + fSumY += pMat2->GetDouble(i,j); + fCount++; + } + } + } + if (fCount < 1.0) + PushNoValue(); + else + { + KahanSum fSumDeltaXDeltaY = 0.0; // sum of (ValX-MeanX)*(ValY-MeanY) + KahanSum fSumSqrDeltaX = 0.0; // sum of (ValX-MeanX)^2 + double fMeanX = fSumX.get() / fCount; + double fMeanY = fSumY.get() / fCount; + for (SCSIZE i = 0; i < nC1; i++) + { + for (SCSIZE j = 0; j < nR1; j++) + { + if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j)) + { + double fValX = pMat1->GetDouble(i,j); + double fValY = pMat2->GetDouble(i,j); + fSumDeltaXDeltaY += (fValX - fMeanX) * (fValY - fMeanY); + fSumSqrDeltaX += (fValX - fMeanX) * (fValX - fMeanX); + } + } + } + if (fSumSqrDeltaX == 0.0) + PushError( FormulaError::DivisionByZero); + else + { + if ( bSlope ) + PushDouble( fSumDeltaXDeltaY.get() / fSumSqrDeltaX.get()); + else + PushDouble( fMeanY - fSumDeltaXDeltaY.get() / fSumSqrDeltaX.get() * fMeanX); + } + } +} + +void ScInterpreter::ScSlope() +{ + CalculateSlopeIntercept(true); +} + +void ScInterpreter::ScIntercept() +{ + CalculateSlopeIntercept(false); +} + +void ScInterpreter::ScForecast() +{ + if ( !MustHaveParamCount( GetByte(), 3 ) ) + return; + ScMatrixRef pMat1 = GetMatrix(); + ScMatrixRef pMat2 = GetMatrix(); + if (!pMat1 || !pMat2) + { + PushIllegalParameter(); + return; + } + SCSIZE nC1, nC2; + SCSIZE nR1, nR2; + pMat1->GetDimensions(nC1, nR1); + pMat2->GetDimensions(nC2, nR2); + if (nR1 != nR2 || nC1 != nC2) + { + PushIllegalArgument(); + return; + } + double fVal = GetDouble(); + // #i78250# numerical stability improved + double fCount = 0.0; + KahanSum fSumX = 0.0; + KahanSum fSumY = 0.0; + + for (SCSIZE i = 0; i < nC1; i++) + { + for (SCSIZE j = 0; j < nR1; j++) + { + if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j)) + { + fSumX += pMat1->GetDouble(i,j); + fSumY += pMat2->GetDouble(i,j); + fCount++; + } + } + } + if (fCount < 1.0) + PushNoValue(); + else + { + KahanSum fSumDeltaXDeltaY = 0.0; // sum of (ValX-MeanX)*(ValY-MeanY) + KahanSum fSumSqrDeltaX = 0.0; // sum of (ValX-MeanX)^2 + double fMeanX = fSumX.get() / fCount; + double fMeanY = fSumY.get() / fCount; + for (SCSIZE i = 0; i < nC1; i++) + { + for (SCSIZE j = 0; j < nR1; j++) + { + if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j)) + { + double fValX = pMat1->GetDouble(i,j); + double fValY = pMat2->GetDouble(i,j); + fSumDeltaXDeltaY += (fValX - fMeanX) * (fValY - fMeanY); + fSumSqrDeltaX += (fValX - fMeanX) * (fValX - fMeanX); + } + } + } + if (fSumSqrDeltaX == 0.0) + PushError( FormulaError::DivisionByZero); + else + PushDouble( fMeanY + fSumDeltaXDeltaY.get() / fSumSqrDeltaX.get() * (fVal - fMeanX)); + } +} + +static void lcl_roundUpNearestPow2(SCSIZE& nNum, SCSIZE& nNumBits) +{ + // Find the least power of 2 that is less than or equal to nNum. + SCSIZE nPow2(1); + nNumBits = std::numeric_limits<SCSIZE>::digits; + nPow2 <<= (nNumBits - 1); + while (nPow2 >= 1) + { + if (nNum & nPow2) + break; + + --nNumBits; + nPow2 >>= 1; + } + + if (nPow2 != nNum) + nNum = nPow2 ? (nPow2 << 1) : 1; + else + --nNumBits; +} + +static SCSIZE lcl_bitReverse(SCSIZE nIn, SCSIZE nBound) +{ + SCSIZE nOut = 0; + for (SCSIZE nMask = 1; nMask < nBound; nMask <<= 1) + { + nOut <<= 1; + + if (nIn & nMask) + nOut |= 1; + } + + return nOut; +} + +namespace { + +// Computes and stores twiddle factors for computing DFT later. +struct ScTwiddleFactors +{ + ScTwiddleFactors(SCSIZE nN, bool bInverse) : + mfWReal(nN), + mfWImag(nN), + mnN(nN), + mbInverse(bInverse) + {} + + void Compute(); + + void Conjugate() + { + mbInverse = !mbInverse; + for (SCSIZE nIdx = 0; nIdx < mnN; ++nIdx) + mfWImag[nIdx] = -mfWImag[nIdx]; + } + + std::vector<double> mfWReal; + std::vector<double> mfWImag; + SCSIZE mnN; + bool mbInverse; +}; + +} + +void ScTwiddleFactors::Compute() +{ + mfWReal.resize(mnN); + mfWImag.resize(mnN); + + double nW = (mbInverse ? 2 : -2)*M_PI/static_cast<double>(mnN); + + if (mnN == 1) + { + mfWReal[0] = 1.0; + mfWImag[0] = 0.0; + } + else if (mnN == 2) + { + mfWReal[0] = 1; + mfWImag[0] = 0; + + mfWReal[1] = -1; + mfWImag[1] = 0; + } + else if (mnN == 4) + { + mfWReal[0] = 1; + mfWImag[0] = 0; + + mfWReal[1] = 0; + mfWImag[1] = (mbInverse ? 1.0 : -1.0); + + mfWReal[2] = -1; + mfWImag[2] = 0; + + mfWReal[3] = 0; + mfWImag[3] = (mbInverse ? -1.0 : 1.0); + } + else if ((mnN % 4) == 0) + { + const SCSIZE nQSize = mnN >> 2; + // Compute cos of the start quadrant. + // This is the first quadrant if mbInverse == true, else it is the fourth quadrant. + for (SCSIZE nIdx = 0; nIdx <= nQSize; ++nIdx) + mfWReal[nIdx] = cos(nW*static_cast<double>(nIdx)); + + if (mbInverse) + { + const SCSIZE nQ1End = nQSize; + // First quadrant + for (SCSIZE nIdx = 0; nIdx <= nQ1End; ++nIdx) + mfWImag[nIdx] = mfWReal[nQ1End-nIdx]; + + // Second quadrant + const SCSIZE nQ2End = nQ1End << 1; + for (SCSIZE nIdx = nQ1End+1; nIdx <= nQ2End; ++nIdx) + { + mfWReal[nIdx] = -mfWReal[nQ2End - nIdx]; + mfWImag[nIdx] = mfWImag[nQ2End - nIdx]; + } + + // Third quadrant + const SCSIZE nQ3End = nQ2End + nQ1End; + for (SCSIZE nIdx = nQ2End+1; nIdx <= nQ3End; ++nIdx) + { + mfWReal[nIdx] = -mfWReal[nIdx - nQ2End]; + mfWImag[nIdx] = -mfWImag[nIdx - nQ2End]; + } + + // Fourth Quadrant + for (SCSIZE nIdx = nQ3End+1; nIdx < mnN; ++nIdx) + { + mfWReal[nIdx] = mfWReal[mnN - nIdx]; + mfWImag[nIdx] = -mfWImag[mnN - nIdx]; + } + } + else + { + const SCSIZE nQ4End = nQSize; + const SCSIZE nQ3End = nQSize << 1; + const SCSIZE nQ2End = nQ3End + nQSize; + + // Fourth quadrant. + for (SCSIZE nIdx = 0; nIdx <= nQ4End; ++nIdx) + mfWImag[nIdx] = -mfWReal[nQ4End - nIdx]; + + // Third quadrant. + for (SCSIZE nIdx = nQ4End+1; nIdx <= nQ3End; ++nIdx) + { + mfWReal[nIdx] = -mfWReal[nQ3End - nIdx]; + mfWImag[nIdx] = mfWImag[nQ3End - nIdx]; + } + + // Second quadrant. + for (SCSIZE nIdx = nQ3End+1; nIdx <= nQ2End; ++nIdx) + { + mfWReal[nIdx] = -mfWReal[nIdx - nQ3End]; + mfWImag[nIdx] = -mfWImag[nIdx - nQ3End]; + } + + // First quadrant. + for (SCSIZE nIdx = nQ2End+1; nIdx < mnN; ++nIdx) + { + mfWReal[nIdx] = mfWReal[mnN - nIdx]; + mfWImag[nIdx] = -mfWImag[mnN - nIdx]; + } + } + } + else + { + for (SCSIZE nIdx = 0; nIdx < mnN; ++nIdx) + { + double fAngle = nW*static_cast<double>(nIdx); + mfWReal[nIdx] = cos(fAngle); + mfWImag[nIdx] = sin(fAngle); + } + } +} + +namespace { + +// A radix-2 decimation in time FFT algorithm for complex valued input. +class ScComplexFFT2 +{ +public: + // rfArray.size() would always be even and a power of 2. (asserted in prepare()) + // rfArray's first half contains the real parts and the later half contains the imaginary parts. + ScComplexFFT2(std::vector<double>& raArray, bool bInverse, bool bPolar, double fMinMag, + ScTwiddleFactors& rTF, bool bSubSampleTFs = false, bool bDisableNormalize = false) : + mrArray(raArray), + mfWReal(rTF.mfWReal), + mfWImag(rTF.mfWImag), + mnPoints(raArray.size()/2), + mnStages(0), + mfMinMag(fMinMag), + mbInverse(bInverse), + mbPolar(bPolar), + mbDisableNormalize(bDisableNormalize), + mbSubSampleTFs(bSubSampleTFs) + {} + + void Compute(); + +private: + + void prepare(); + + double getReal(SCSIZE nIdx) + { + return mrArray[nIdx]; + } + + void setReal(double fVal, SCSIZE nIdx) + { + mrArray[nIdx] = fVal; + } + + double getImag(SCSIZE nIdx) + { + return mrArray[mnPoints + nIdx]; + } + + void setImag(double fVal, SCSIZE nIdx) + { + mrArray[mnPoints + nIdx] = fVal; + } + + SCSIZE getTFactorIndex(SCSIZE nPtIndex, SCSIZE nTfIdxScaleBits) + { + return ( ( nPtIndex << nTfIdxScaleBits ) & ( mnPoints - 1 ) ); // (x & (N-1)) is same as (x % N) but faster. + } + + void computeFly(SCSIZE nTopIdx, SCSIZE nBottomIdx, SCSIZE nWIdx1, SCSIZE nWIdx2) + { + if (mbSubSampleTFs) + { + nWIdx1 <<= 1; + nWIdx2 <<= 1; + } + + const double x1r = getReal(nTopIdx); + const double x2r = getReal(nBottomIdx); + + const double& w1r = mfWReal[nWIdx1]; + const double& w1i = mfWImag[nWIdx1]; + + const double& w2r = mfWReal[nWIdx2]; + const double& w2i = mfWImag[nWIdx2]; + + const double x1i = getImag(nTopIdx); + const double x2i = getImag(nBottomIdx); + + setReal(x1r + x2r*w1r - x2i*w1i, nTopIdx); + setImag(x1i + x2i*w1r + x2r*w1i, nTopIdx); + + setReal(x1r + x2r*w2r - x2i*w2i, nBottomIdx); + setImag(x1i + x2i*w2r + x2r*w2i, nBottomIdx); + } + + std::vector<double>& mrArray; + std::vector<double>& mfWReal; + std::vector<double>& mfWImag; + SCSIZE mnPoints; + SCSIZE mnStages; + double mfMinMag; + bool mbInverse:1; + bool mbPolar:1; + bool mbDisableNormalize:1; + bool mbSubSampleTFs:1; +}; + +} + +void ScComplexFFT2::prepare() +{ + SCSIZE nPoints = mnPoints; + lcl_roundUpNearestPow2(nPoints, mnStages); + assert(nPoints == mnPoints); + + // Reorder array by bit-reversed indices. + for (SCSIZE nIdx = 0; nIdx < mnPoints; ++nIdx) + { + SCSIZE nRevIdx = lcl_bitReverse(nIdx, mnPoints); + if (nIdx < nRevIdx) + { + double fTmp = getReal(nIdx); + setReal(getReal(nRevIdx), nIdx); + setReal(fTmp, nRevIdx); + + fTmp = getImag(nIdx); + setImag(getImag(nRevIdx), nIdx); + setImag(fTmp, nRevIdx); + } + } +} + +static void lcl_normalize(std::vector<double>& rCmplxArray, bool bScaleOnlyReal) +{ + const SCSIZE nPoints = rCmplxArray.size()/2; + const double fScale = 1.0/static_cast<double>(nPoints); + + // Scale the real part + for (SCSIZE nIdx = 0; nIdx < nPoints; ++nIdx) + rCmplxArray[nIdx] *= fScale; + + if (!bScaleOnlyReal) + { + const SCSIZE nLen = nPoints*2; + for (SCSIZE nIdx = nPoints; nIdx < nLen; ++nIdx) + rCmplxArray[nIdx] *= fScale; + } +} + +static void lcl_convertToPolar(std::vector<double>& rCmplxArray, double fMinMag) +{ + const SCSIZE nPoints = rCmplxArray.size()/2; + double fMag, fPhase, fR, fI; + for (SCSIZE nIdx = 0; nIdx < nPoints; ++nIdx) + { + fR = rCmplxArray[nIdx]; + fI = rCmplxArray[nPoints+nIdx]; + fMag = std::hypot(fR, fI); + if (fMag < fMinMag) + { + fMag = 0.0; + fPhase = 0.0; + } + else + { + fPhase = atan2(fI, fR); + } + + rCmplxArray[nIdx] = fMag; + rCmplxArray[nPoints+nIdx] = fPhase; + } +} + +void ScComplexFFT2::Compute() +{ + prepare(); + + const SCSIZE nFliesInStage = mnPoints/2; + for (SCSIZE nStage = 0; nStage < mnStages; ++nStage) + { + const SCSIZE nTFIdxScaleBits = mnStages - nStage - 1; // Twiddle factor index's scale factor in bits. + const SCSIZE nFliesInGroup = SCSIZE(1) << nStage; + const SCSIZE nGroups = nFliesInStage/nFliesInGroup; + const SCSIZE nFlyWidth = nFliesInGroup; + for (SCSIZE nGroup = 0, nFlyTopIdx = 0; nGroup < nGroups; ++nGroup) + { + for (SCSIZE nFly = 0; nFly < nFliesInGroup; ++nFly, ++nFlyTopIdx) + { + SCSIZE nFlyBottomIdx = nFlyTopIdx + nFlyWidth; + SCSIZE nWIdx1 = getTFactorIndex(nFlyTopIdx, nTFIdxScaleBits); + SCSIZE nWIdx2 = getTFactorIndex(nFlyBottomIdx, nTFIdxScaleBits); + + computeFly(nFlyTopIdx, nFlyBottomIdx, nWIdx1, nWIdx2); + } + + nFlyTopIdx += nFlyWidth; + } + } + + if (mbPolar) + lcl_convertToPolar(mrArray, mfMinMag); + + // Normalize after converting to polar, so we have a chance to + // save O(mnPoints) flops. + if (mbInverse && !mbDisableNormalize) + lcl_normalize(mrArray, mbPolar); +} + +namespace { + +// Bluestein's algorithm or chirp z-transform algorithm that can be used to +// compute DFT of a complex valued input of any length N in O(N lgN) time. +class ScComplexBluesteinFFT +{ +public: + + ScComplexBluesteinFFT(std::vector<double>& rArray, bool bReal, bool bInverse, + bool bPolar, double fMinMag, bool bDisableNormalize = false) : + mrArray(rArray), + mnPoints(rArray.size()/2), // rArray should have space for imaginary parts even if real input. + mfMinMag(fMinMag), + mbReal(bReal), + mbInverse(bInverse), + mbPolar(bPolar), + mbDisableNormalize(bDisableNormalize) + {} + + void Compute(); + +private: + std::vector<double>& mrArray; + const SCSIZE mnPoints; + double mfMinMag; + bool mbReal:1; + bool mbInverse:1; + bool mbPolar:1; + bool mbDisableNormalize:1; +}; + +} + +void ScComplexBluesteinFFT::Compute() +{ + std::vector<double> aRealScalars(mnPoints); + std::vector<double> aImagScalars(mnPoints); + double fW = (mbInverse ? 2 : -2)*M_PI/static_cast<double>(mnPoints); + for (SCSIZE nIdx = 0; nIdx < mnPoints; ++nIdx) + { + double fAngle = 0.5*fW*static_cast<double>(nIdx*nIdx); + aRealScalars[nIdx] = cos(fAngle); + aImagScalars[nIdx] = sin(fAngle); + } + + SCSIZE nMinSize = mnPoints*2 - 1; + SCSIZE nExtendedLength = nMinSize, nTmp = 0; + lcl_roundUpNearestPow2(nExtendedLength, nTmp); + std::vector<double> aASignal(nExtendedLength*2); // complex valued + std::vector<double> aBSignal(nExtendedLength*2); // complex valued + + double fReal, fImag; + for (SCSIZE nIdx = 0; nIdx < mnPoints; ++nIdx) + { + // Real part of A signal. + aASignal[nIdx] = mrArray[nIdx]*aRealScalars[nIdx] + (mbReal ? 0.0 : -mrArray[mnPoints+nIdx]*aImagScalars[nIdx]); + // Imaginary part of A signal. + aASignal[nExtendedLength + nIdx] = mrArray[nIdx]*aImagScalars[nIdx] + (mbReal ? 0.0 : mrArray[mnPoints+nIdx]*aRealScalars[nIdx]); + + // Real part of B signal. + aBSignal[nIdx] = fReal = aRealScalars[nIdx]; + // Imaginary part of B signal. + aBSignal[nExtendedLength + nIdx] = fImag = -aImagScalars[nIdx]; // negative sign because B signal is the conjugation of the scalars. + + if (nIdx) + { + // B signal needs a mirror of its part in 0 < n < mnPoints at the tail end. + aBSignal[nExtendedLength - nIdx] = fReal; + aBSignal[(nExtendedLength<<1) - nIdx] = fImag; + } + } + + { + ScTwiddleFactors aTF(nExtendedLength, false /*not inverse*/); + aTF.Compute(); + + // Do complex-FFT2 of both A and B signal. + ScComplexFFT2 aFFT2A(aASignal, false /*not inverse*/, false /*no polar*/, 0.0 /* no clipping */, + aTF, false /*no subsample*/, true /* disable normalize */); + aFFT2A.Compute(); + + ScComplexFFT2 aFFT2B(aBSignal, false /*not inverse*/, false /*no polar*/, 0.0 /* no clipping */, + aTF, false /*no subsample*/, true /* disable normalize */); + aFFT2B.Compute(); + + double fAR, fAI, fBR, fBI; + for (SCSIZE nIdx = 0; nIdx < nExtendedLength; ++nIdx) + { + fAR = aASignal[nIdx]; + fAI = aASignal[nExtendedLength + nIdx]; + fBR = aBSignal[nIdx]; + fBI = aBSignal[nExtendedLength + nIdx]; + + // Do point-wise product inplace in A signal. + aASignal[nIdx] = fAR*fBR - fAI*fBI; + aASignal[nExtendedLength + nIdx] = fAR*fBI + fAI*fBR; + } + + // Do complex-inverse-FFT2 of aASignal. + aTF.Conjugate(); + ScComplexFFT2 aFFT2AI(aASignal, true /*inverse*/, false /*no polar*/, 0.0 /* no clipping */, aTF); // Need normalization here. + aFFT2AI.Compute(); + } + + // Point-wise multiply with scalars. + for (SCSIZE nIdx = 0; nIdx < mnPoints; ++nIdx) + { + fReal = aASignal[nIdx]; + fImag = aASignal[nExtendedLength + nIdx]; + mrArray[nIdx] = fReal*aRealScalars[nIdx] - fImag*aImagScalars[nIdx]; // no conjugation needed here. + mrArray[mnPoints + nIdx] = fReal*aImagScalars[nIdx] + fImag*aRealScalars[nIdx]; + } + + // Normalize/Polar operations + if (mbPolar) + lcl_convertToPolar(mrArray, mfMinMag); + + // Normalize after converting to polar, so we have a chance to + // save O(mnPoints) flops. + if (mbInverse && !mbDisableNormalize) + lcl_normalize(mrArray, mbPolar); +} + +namespace { + +// Computes DFT of an even length(N) real-valued input by using a +// ScComplexFFT2 if N == 2^k for some k or else by using a ScComplexBluesteinFFT +// with a complex valued input of length = N/2. +class ScRealFFT +{ +public: + + ScRealFFT(std::vector<double>& rInArray, std::vector<double>& rOutArray, bool bInverse, + bool bPolar, double fMinMag) : + mrInArray(rInArray), + mrOutArray(rOutArray), + mfMinMag(fMinMag), + mbInverse(bInverse), + mbPolar(bPolar) + {} + + void Compute(); + +private: + std::vector<double>& mrInArray; + std::vector<double>& mrOutArray; + double mfMinMag; + bool mbInverse:1; + bool mbPolar:1; +}; + +} + +void ScRealFFT::Compute() +{ + // input length has to be even to do this optimization. + assert(mrInArray.size() % 2 == 0); + assert(mrInArray.size()*2 == mrOutArray.size()); + // nN is the number of points in the complex-fft input + // which will be half of the number of points in real array. + const SCSIZE nN = mrInArray.size()/2; + if (nN == 0) + { + mrOutArray[0] = mrInArray[0]; + mrOutArray[1] = 0.0; + return; + } + + // work array should be the same length as mrInArray + std::vector<double> aWorkArray(nN*2); + for (SCSIZE nIdx = 0; nIdx < nN; ++nIdx) + { + SCSIZE nDoubleIdx = 2*nIdx; + // Use even elements as real part + aWorkArray[nIdx] = mrInArray[nDoubleIdx]; + // and odd elements as imaginary part of the contrived complex sequence. + aWorkArray[nN+nIdx] = mrInArray[nDoubleIdx+1]; + } + + ScTwiddleFactors aTFs(nN*2, mbInverse); + aTFs.Compute(); + SCSIZE nNextPow2 = nN, nTmp = 0; + lcl_roundUpNearestPow2(nNextPow2, nTmp); + + if (nNextPow2 == nN) + { + ScComplexFFT2 aFFT2(aWorkArray, mbInverse, false /*disable polar*/, 0.0 /* no clipping */, + aTFs, true /*subsample tf*/, true /*disable normalize*/); + aFFT2.Compute(); + } + else + { + ScComplexBluesteinFFT aFFT(aWorkArray, false /*complex input*/, mbInverse, false /*disable polar*/, + 0.0 /* no clipping */, true /*disable normalize*/); + aFFT.Compute(); + } + + // Post process aWorkArray to populate mrOutArray + + const SCSIZE nTwoN = 2*nN, nThreeN = 3*nN; + double fY1R, fY2R, fY1I, fY2I, fResR, fResI, fWR, fWI; + for (SCSIZE nIdx = 0; nIdx < nN; ++nIdx) + { + const SCSIZE nIdxRev = nIdx ? (nN - nIdx) : 0; + fY1R = aWorkArray[nIdx]; + fY2R = aWorkArray[nIdxRev]; + fY1I = aWorkArray[nN + nIdx]; + fY2I = aWorkArray[nN + nIdxRev]; + fWR = aTFs.mfWReal[nIdx]; + fWI = aTFs.mfWImag[nIdx]; + + // mrOutArray has length = 4*nN + // Real part of the final output (only half of the symmetry around Nyquist frequency) + // Fills the first quarter. + mrOutArray[nIdx] = fResR = 0.5*( + fY1R + fY2R + + fWR * (fY1I + fY2I) + + fWI * (fY1R - fY2R) ); + // Imaginary part of the final output (only half of the symmetry around Nyquist frequency) + // Fills the third quarter. + mrOutArray[nTwoN + nIdx] = fResI = 0.5*( + fY1I - fY2I + + fWI * (fY1I + fY2I) - + fWR * (fY1R - fY2R) ); + + // Fill the missing 2 quarters using symmetry argument. + if (nIdx) + { + // Fills the 2nd quarter. + mrOutArray[nN + nIdxRev] = fResR; + // Fills the 4th quarter. + mrOutArray[nThreeN + nIdxRev] = -fResI; + } + else + { + mrOutArray[nN] = fY1R - fY1I; + mrOutArray[nThreeN] = 0.0; + } + } + + // Normalize/Polar operations + if (mbPolar) + lcl_convertToPolar(mrOutArray, mfMinMag); + + // Normalize after converting to polar, so we have a chance to + // save O(mnPoints) flops. + if (mbInverse) + lcl_normalize(mrOutArray, mbPolar); +} + +using ScMatrixGenerator = ScMatrixRef(SCSIZE, SCSIZE, std::vector<double>&); + +namespace { + +// Generic FFT class that decides which FFT implementation to use. +class ScFFT +{ +public: + + ScFFT(ScMatrixRef& pMat, bool bReal, bool bInverse, bool bPolar, double fMinMag) : + mpInputMat(pMat), + mfMinMag(fMinMag), + mbReal(bReal), + mbInverse(bInverse), + mbPolar(bPolar) + {} + + ScMatrixRef Compute(const std::function<ScMatrixGenerator>& rMatGenFunc); + +private: + ScMatrixRef& mpInputMat; + double mfMinMag; + bool mbReal:1; + bool mbInverse:1; + bool mbPolar:1; +}; + +} + +ScMatrixRef ScFFT::Compute(const std::function<ScMatrixGenerator>& rMatGenFunc) +{ + std::vector<double> aArray; + mpInputMat->GetDoubleArray(aArray); + SCSIZE nPoints = mbReal ? aArray.size() : (aArray.size()/2); + if (nPoints == 1) + { + std::vector<double> aOutArray(2); + aOutArray[0] = aArray[0]; + aOutArray[1] = mbReal ? 0.0 : aArray[1]; + if (mbPolar) + lcl_convertToPolar(aOutArray, mfMinMag); + return rMatGenFunc(2, 1, aOutArray); + } + + if (mbReal && (nPoints % 2) == 0) + { + std::vector<double> aOutArray(nPoints*2); + ScRealFFT aFFT(aArray, aOutArray, mbInverse, mbPolar, mfMinMag); + aFFT.Compute(); + return rMatGenFunc(2, nPoints, aOutArray); + } + + SCSIZE nNextPow2 = nPoints, nTmp = 0; + lcl_roundUpNearestPow2(nNextPow2, nTmp); + if (nNextPow2 == nPoints && !mbReal) + { + ScTwiddleFactors aTF(nPoints, mbInverse); + aTF.Compute(); + ScComplexFFT2 aFFT2(aArray, mbInverse, mbPolar, mfMinMag, aTF); + aFFT2.Compute(); + return rMatGenFunc(2, nPoints, aArray); + } + + if (mbReal) + aArray.resize(nPoints*2, 0.0); + ScComplexBluesteinFFT aFFT(aArray, mbReal, mbInverse, mbPolar, mfMinMag); + aFFT.Compute(); + return rMatGenFunc(2, nPoints, aArray); +} + +void ScInterpreter::ScFourier() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2, 5 ) ) + return; + + bool bInverse = false; + bool bPolar = false; + double fMinMag = 0.0; + + if (nParamCount == 5) + { + if (IsMissing()) + Pop(); + else + fMinMag = GetDouble(); + } + + if (nParamCount >= 4) + { + if (IsMissing()) + Pop(); + else + bPolar = GetBool(); + } + + if (nParamCount >= 3) + { + if (IsMissing()) + Pop(); + else + bInverse = GetBool(); + } + + bool bGroupedByColumn = GetBool(); + + ScMatrixRef pInputMat = GetMatrix(); + if (!pInputMat) + { + PushIllegalParameter(); + return; + } + + SCSIZE nC, nR; + pInputMat->GetDimensions(nC, nR); + + if ((bGroupedByColumn && nC > 2) || (!bGroupedByColumn && nR > 2)) + { + // There can be no more than 2 columns (real, imaginary) if data grouped by columns. + // and no more than 2 rows if data is grouped by rows. + PushIllegalArgument(); + return; + } + + if (!pInputMat->IsNumeric()) + { + PushNoValue(); + return; + } + + bool bRealInput = true; + if (!bGroupedByColumn) + { + pInputMat->MatTrans(*pInputMat); + bRealInput = (nR == 1); + } + else + { + bRealInput = (nC == 1); + } + + ScFFT aFFT(pInputMat, bRealInput, bInverse, bPolar, fMinMag); + std::function<ScMatrixGenerator> aFunc = [this](SCSIZE nCol, SCSIZE nRow, std::vector<double>& rVec) -> ScMatrixRef + { + return this->GetNewMat(nCol, nRow, rVec); + }; + ScMatrixRef pOut = aFFT.Compute(aFunc); + PushMatrix(pOut); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/interpr4.cxx b/sc/source/core/tool/interpr4.cxx new file mode 100644 index 0000000000..95dff9f1cc --- /dev/null +++ b/sc/source/core/tool/interpr4.cxx @@ -0,0 +1,4839 @@ +/* -*- 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 <interpre.hxx> + +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <rtl/math.hxx> +#include <sfx2/app.hxx> +#include <sfx2/objsh.hxx> +#include <basic/sbmeth.hxx> +#include <basic/sbmod.hxx> +#include <basic/sbstar.hxx> +#include <basic/sbx.hxx> +#include <basic/sbxobj.hxx> +#include <basic/sbuno.hxx> +#include <osl/thread.h> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <svl/sharedstringpool.hxx> +#include <unotools/charclass.hxx> +#include <stdlib.h> +#include <string.h> + +#include <com/sun/star/table/XCellRange.hpp> +#include <com/sun/star/script/XInvocation.hpp> +#include <com/sun/star/sheet/XSheetCellRange.hpp> + +#include <global.hxx> +#include <dbdata.hxx> +#include <formulacell.hxx> +#include <callform.hxx> +#include <addincol.hxx> +#include <document.hxx> +#include <dociter.hxx> +#include <docsh.hxx> +#include <docoptio.hxx> +#include <scmatrix.hxx> +#include <adiasync.hxx> +#include <cellsuno.hxx> +#include <optuno.hxx> +#include <rangeseq.hxx> +#include <addinlis.hxx> +#include <jumpmatrix.hxx> +#include <parclass.hxx> +#include <externalrefmgr.hxx> +#include <formula/FormulaCompiler.hxx> +#include <macromgr.hxx> +#include <doubleref.hxx> +#include <queryparam.hxx> +#include <tokenarray.hxx> +#include <compiler.hxx> + +#include <map> +#include <algorithm> +#include <basic/basmgr.hxx> +#include <vbahelper/vbaaccesshelper.hxx> +#include <memory> + +using namespace com::sun::star; +using namespace formula; +using ::std::unique_ptr; + +#define ADDIN_MAXSTRLEN 256 + +thread_local std::unique_ptr<ScTokenStack> ScInterpreter::pGlobalStack; +thread_local bool ScInterpreter::bGlobalStackInUse = false; + +// document access functions + +void ScInterpreter::ReplaceCell( ScAddress& rPos ) +{ + size_t ListSize = mrDoc.m_TableOpList.size(); + for ( size_t i = 0; i < ListSize; ++i ) + { + ScInterpreterTableOpParams *const pTOp = mrDoc.m_TableOpList[ i ]; + if ( rPos == pTOp->aOld1 ) + { + rPos = pTOp->aNew1; + return ; + } + else if ( rPos == pTOp->aOld2 ) + { + rPos = pTOp->aNew2; + return ; + } + } +} + +bool ScInterpreter::IsTableOpInRange( const ScRange& rRange ) +{ + if ( rRange.aStart == rRange.aEnd ) + return false; // not considered to be a range in TableOp sense + + // we can't replace a single cell in a range + size_t ListSize = mrDoc.m_TableOpList.size(); + for ( size_t i = 0; i < ListSize; ++i ) + { + ScInterpreterTableOpParams *const pTOp = mrDoc.m_TableOpList[ i ]; + if ( rRange.Contains( pTOp->aOld1 ) ) + return true; + if ( rRange.Contains( pTOp->aOld2 ) ) + return true; + } + return false; +} + +sal_uInt32 ScInterpreter::GetCellNumberFormat( const ScAddress& rPos, ScRefCellValue& rCell ) +{ + sal_uInt32 nFormat; + FormulaError nErr; + if (rCell.isEmpty()) + { + nFormat = mrDoc.GetNumberFormat( mrContext, rPos ); + nErr = FormulaError::NONE; + } + else + { + if (rCell.getType() == CELLTYPE_FORMULA) + nErr = rCell.getFormula()->GetErrCode(); + else + nErr = FormulaError::NONE; + nFormat = mrDoc.GetNumberFormat( mrContext, rPos ); + } + + SetError(nErr); + return nFormat; +} + +/// Only ValueCell, formula cells already store the result rounded. +double ScInterpreter::GetValueCellValue( const ScAddress& rPos, double fOrig ) +{ + if ( bCalcAsShown && fOrig != 0.0 ) + { + sal_uInt32 nFormat = mrDoc.GetNumberFormat( mrContext, rPos ); + fOrig = mrDoc.RoundValueAsShown( fOrig, nFormat, &mrContext ); + } + return fOrig; +} + +FormulaError ScInterpreter::GetCellErrCode( const ScRefCellValue& rCell ) +{ + return rCell.getType() == CELLTYPE_FORMULA ? rCell.getFormula()->GetErrCode() : FormulaError::NONE; +} + +double ScInterpreter::ConvertStringToValue( const OUString& rStr ) +{ + FormulaError nError = FormulaError::NONE; + double fValue = ScGlobal::ConvertStringToValue( rStr, maCalcConfig, nError, mnStringNoValueError, + pFormatter, nCurFmtType); + if (nError != FormulaError::NONE) + SetError(nError); + return fValue; +} + +double ScInterpreter::ConvertStringToValue( const OUString& rStr, FormulaError& rError, SvNumFormatType& rCurFmtType ) +{ + return ScGlobal::ConvertStringToValue( rStr, maCalcConfig, rError, mnStringNoValueError, pFormatter, rCurFmtType); +} + +double ScInterpreter::GetCellValue( const ScAddress& rPos, ScRefCellValue& rCell ) +{ + FormulaError nErr = nGlobalError; + nGlobalError = FormulaError::NONE; + double nVal = GetCellValueOrZero(rPos, rCell); + // Propagate previous error, if any; nGlobalError==CellNoValue is not an + // error here, preserve previous error or non-error. + if (nErr != FormulaError::NONE || nGlobalError == FormulaError::CellNoValue) + nGlobalError = nErr; + return nVal; +} + +double ScInterpreter::GetCellValueOrZero( const ScAddress& rPos, ScRefCellValue& rCell ) +{ + double fValue = 0.0; + + CellType eType = rCell.getType(); + switch (eType) + { + case CELLTYPE_FORMULA: + { + ScFormulaCell* pFCell = rCell.getFormula(); + FormulaError nErr = pFCell->GetErrCode(); + if( nErr == FormulaError::NONE ) + { + if (pFCell->IsValue()) + { + fValue = pFCell->GetValue(); + mrDoc.GetNumberFormatInfo( mrContext, nCurFmtType, nCurFmtIndex, + rPos ); + } + else + { + fValue = ConvertStringToValue(pFCell->GetString().getString()); + } + } + else + { + fValue = 0.0; + SetError(nErr); + } + } + break; + case CELLTYPE_VALUE: + { + fValue = rCell.getDouble(); + nCurFmtIndex = mrDoc.GetNumberFormat( mrContext, rPos ); + nCurFmtType = mrContext.GetNumberFormatType( nCurFmtIndex ); + if ( bCalcAsShown && fValue != 0.0 ) + fValue = mrDoc.RoundValueAsShown( fValue, nCurFmtIndex, &mrContext ); + } + break; + case CELLTYPE_STRING: + case CELLTYPE_EDIT: + { + // SUM(A1:A2) differs from A1+A2. No good. But people insist on + // it ... #i5658# + OUString aStr = rCell.getString(&mrDoc); + fValue = ConvertStringToValue( aStr ); + } + break; + case CELLTYPE_NONE: + fValue = 0.0; // empty or broadcaster cell + break; + } + + return fValue; +} + +void ScInterpreter::GetCellString( svl::SharedString& rStr, ScRefCellValue& rCell ) +{ + FormulaError nErr = FormulaError::NONE; + + switch (rCell.getType()) + { + case CELLTYPE_STRING: + case CELLTYPE_EDIT: + rStr = mrStrPool.intern(rCell.getString(&mrDoc)); + break; + case CELLTYPE_FORMULA: + { + ScFormulaCell* pFCell = rCell.getFormula(); + nErr = pFCell->GetErrCode(); + if (pFCell->IsValue()) + { + rStr = GetStringFromDouble( pFCell->GetValue() ); + } + else + rStr = pFCell->GetString(); + } + break; + case CELLTYPE_VALUE: + { + rStr = GetStringFromDouble( rCell.getDouble() ); + } + break; + default: + rStr = svl::SharedString::getEmptyString(); + break; + } + + SetError(nErr); +} + +bool ScInterpreter::CreateDoubleArr(SCCOL nCol1, SCROW nRow1, SCTAB nTab1, + SCCOL nCol2, SCROW nRow2, SCTAB nTab2, sal_uInt8* pCellArr) +{ + + // Old Add-Ins are hard limited to sal_uInt16 values. + static_assert(MAXCOLCOUNT <= SAL_MAX_UINT16 && MAXCOLCOUNT_JUMBO <= SAL_MAX_UINT16, + "Add check for columns > SAL_MAX_UINT16!"); + if (nRow1 > SAL_MAX_UINT16 || nRow2 > SAL_MAX_UINT16) + return false; + + sal_uInt16 nCount = 0; + sal_uInt16* p = reinterpret_cast<sal_uInt16*>(pCellArr); + *p++ = static_cast<sal_uInt16>(nCol1); + *p++ = static_cast<sal_uInt16>(nRow1); + *p++ = static_cast<sal_uInt16>(nTab1); + *p++ = static_cast<sal_uInt16>(nCol2); + *p++ = static_cast<sal_uInt16>(nRow2); + *p++ = static_cast<sal_uInt16>(nTab2); + sal_uInt16* pCount = p; + *p++ = 0; + sal_uInt16 nPos = 14; + SCTAB nTab = nTab1; + ScAddress aAdr; + while (nTab <= nTab2) + { + aAdr.SetTab( nTab ); + SCROW nRow = nRow1; + while (nRow <= nRow2) + { + aAdr.SetRow( nRow ); + SCCOL nCol = nCol1; + while (nCol <= nCol2) + { + aAdr.SetCol( nCol ); + + ScRefCellValue aCell(mrDoc, aAdr); + if (!aCell.isEmpty()) + { + FormulaError nErr = FormulaError::NONE; + double nVal = 0.0; + bool bOk = true; + switch (aCell.getType()) + { + case CELLTYPE_VALUE : + nVal = GetValueCellValue(aAdr, aCell.getDouble()); + break; + case CELLTYPE_FORMULA : + if (aCell.getFormula()->IsValue()) + { + nErr = aCell.getFormula()->GetErrCode(); + nVal = aCell.getFormula()->GetValue(); + } + else + bOk = false; + break; + default : + bOk = false; + break; + } + if (bOk) + { + if ((nPos + (4 * sizeof(sal_uInt16)) + sizeof(double)) > MAXARRSIZE) + return false; + *p++ = static_cast<sal_uInt16>(nCol); + *p++ = static_cast<sal_uInt16>(nRow); + *p++ = static_cast<sal_uInt16>(nTab); + *p++ = static_cast<sal_uInt16>(nErr); + memcpy( p, &nVal, sizeof(double)); + nPos += 8 + sizeof(double); + p = reinterpret_cast<sal_uInt16*>( pCellArr + nPos ); + nCount++; + } + } + nCol++; + } + nRow++; + } + nTab++; + } + *pCount = nCount; + return true; +} + +bool ScInterpreter::CreateStringArr(SCCOL nCol1, SCROW nRow1, SCTAB nTab1, + SCCOL nCol2, SCROW nRow2, SCTAB nTab2, + sal_uInt8* pCellArr) +{ + + // Old Add-Ins are hard limited to sal_uInt16 values. + static_assert(MAXCOLCOUNT <= SAL_MAX_UINT16 && MAXCOLCOUNT_JUMBO <= SAL_MAX_UINT16, + "Add check for columns > SAL_MAX_UINT16!"); + if (nRow1 > SAL_MAX_UINT16 || nRow2 > SAL_MAX_UINT16) + return false; + + sal_uInt16 nCount = 0; + sal_uInt16* p = reinterpret_cast<sal_uInt16*>(pCellArr); + *p++ = static_cast<sal_uInt16>(nCol1); + *p++ = static_cast<sal_uInt16>(nRow1); + *p++ = static_cast<sal_uInt16>(nTab1); + *p++ = static_cast<sal_uInt16>(nCol2); + *p++ = static_cast<sal_uInt16>(nRow2); + *p++ = static_cast<sal_uInt16>(nTab2); + sal_uInt16* pCount = p; + *p++ = 0; + sal_uInt16 nPos = 14; + SCTAB nTab = nTab1; + while (nTab <= nTab2) + { + SCROW nRow = nRow1; + while (nRow <= nRow2) + { + SCCOL nCol = nCol1; + while (nCol <= nCol2) + { + ScRefCellValue aCell(mrDoc, ScAddress(nCol, nRow, nTab)); + if (!aCell.isEmpty()) + { + OUString aStr; + FormulaError nErr = FormulaError::NONE; + bool bOk = true; + switch (aCell.getType()) + { + case CELLTYPE_STRING: + case CELLTYPE_EDIT: + aStr = aCell.getString(&mrDoc); + break; + case CELLTYPE_FORMULA: + if (!aCell.getFormula()->IsValue()) + { + nErr = aCell.getFormula()->GetErrCode(); + aStr = aCell.getFormula()->GetString().getString(); + } + else + bOk = false; + break; + default : + bOk = false; + break; + } + if (bOk) + { + OString aTmp(OUStringToOString(aStr, + osl_getThreadTextEncoding())); + // Old Add-Ins are limited to sal_uInt16 string + // lengths, and room for pad byte check. + if ( aTmp.getLength() > SAL_MAX_UINT16 - 2 ) + return false; + // Append a 0-pad-byte if string length is odd + // MUST be sal_uInt16 + sal_uInt16 nStrLen = static_cast<sal_uInt16>(aTmp.getLength()); + sal_uInt16 nLen = ( nStrLen + 2 ) & ~1; + + if ((static_cast<sal_uLong>(nPos) + (5 * sizeof(sal_uInt16)) + nLen) > MAXARRSIZE) + return false; + *p++ = static_cast<sal_uInt16>(nCol); + *p++ = static_cast<sal_uInt16>(nRow); + *p++ = static_cast<sal_uInt16>(nTab); + *p++ = static_cast<sal_uInt16>(nErr); + *p++ = nLen; + memcpy( p, aTmp.getStr(), nStrLen + 1); + nPos += 10 + nStrLen + 1; + sal_uInt8* q = pCellArr + nPos; + if( (nStrLen & 1) == 0 ) + { + *q++ = 0; + nPos++; + } + p = reinterpret_cast<sal_uInt16*>( pCellArr + nPos ); + nCount++; + } + } + nCol++; + } + nRow++; + } + nTab++; + } + *pCount = nCount; + return true; +} + +bool ScInterpreter::CreateCellArr(SCCOL nCol1, SCROW nRow1, SCTAB nTab1, + SCCOL nCol2, SCROW nRow2, SCTAB nTab2, + sal_uInt8* pCellArr) +{ + + // Old Add-Ins are hard limited to sal_uInt16 values. + static_assert(MAXCOLCOUNT <= SAL_MAX_UINT16 && MAXCOLCOUNT_JUMBO <= SAL_MAX_UINT16, + "Add check for columns > SAL_MAX_UINT16!"); + if (nRow1 > SAL_MAX_UINT16 || nRow2 > SAL_MAX_UINT16) + return false; + + sal_uInt16 nCount = 0; + sal_uInt16* p = reinterpret_cast<sal_uInt16*>(pCellArr); + *p++ = static_cast<sal_uInt16>(nCol1); + *p++ = static_cast<sal_uInt16>(nRow1); + *p++ = static_cast<sal_uInt16>(nTab1); + *p++ = static_cast<sal_uInt16>(nCol2); + *p++ = static_cast<sal_uInt16>(nRow2); + *p++ = static_cast<sal_uInt16>(nTab2); + sal_uInt16* pCount = p; + *p++ = 0; + sal_uInt16 nPos = 14; + SCTAB nTab = nTab1; + ScAddress aAdr; + while (nTab <= nTab2) + { + aAdr.SetTab( nTab ); + SCROW nRow = nRow1; + while (nRow <= nRow2) + { + aAdr.SetRow( nRow ); + SCCOL nCol = nCol1; + while (nCol <= nCol2) + { + aAdr.SetCol( nCol ); + ScRefCellValue aCell(mrDoc, aAdr); + if (!aCell.isEmpty()) + { + FormulaError nErr = FormulaError::NONE; + sal_uInt16 nType = 0; // 0 = number; 1 = string + double nVal = 0.0; + OUString aStr; + bool bOk = true; + switch (aCell.getType()) + { + case CELLTYPE_STRING : + case CELLTYPE_EDIT : + aStr = aCell.getString(&mrDoc); + nType = 1; + break; + case CELLTYPE_VALUE : + nVal = GetValueCellValue(aAdr, aCell.getDouble()); + break; + case CELLTYPE_FORMULA : + nErr = aCell.getFormula()->GetErrCode(); + if (aCell.getFormula()->IsValue()) + nVal = aCell.getFormula()->GetValue(); + else + aStr = aCell.getFormula()->GetString().getString(); + break; + default : + bOk = false; + break; + } + if (bOk) + { + if ((nPos + (5 * sizeof(sal_uInt16))) > MAXARRSIZE) + return false; + *p++ = static_cast<sal_uInt16>(nCol); + *p++ = static_cast<sal_uInt16>(nRow); + *p++ = static_cast<sal_uInt16>(nTab); + *p++ = static_cast<sal_uInt16>(nErr); + *p++ = nType; + nPos += 10; + if (nType == 0) + { + if ((nPos + sizeof(double)) > MAXARRSIZE) + return false; + memcpy( p, &nVal, sizeof(double)); + nPos += sizeof(double); + } + else + { + OString aTmp(OUStringToOString(aStr, + osl_getThreadTextEncoding())); + // Old Add-Ins are limited to sal_uInt16 string + // lengths, and room for pad byte check. + if ( aTmp.getLength() > SAL_MAX_UINT16 - 2 ) + return false; + // Append a 0-pad-byte if string length is odd + // MUST be sal_uInt16 + sal_uInt16 nStrLen = static_cast<sal_uInt16>(aTmp.getLength()); + sal_uInt16 nLen = ( nStrLen + 2 ) & ~1; + if ( (static_cast<sal_uLong>(nPos) + 2 + nLen) > MAXARRSIZE) + return false; + *p++ = nLen; + memcpy( p, aTmp.getStr(), nStrLen + 1); + nPos += 2 + nStrLen + 1; + sal_uInt8* q = pCellArr + nPos; + if( (nStrLen & 1) == 0 ) + { + *q++ = 0; + nPos++; + } + } + nCount++; + p = reinterpret_cast<sal_uInt16*>( pCellArr + nPos ); + } + } + nCol++; + } + nRow++; + } + nTab++; + } + *pCount = nCount; + return true; +} + +// Stack operations + +// Also releases a TempToken if appropriate. + +void ScInterpreter::PushWithoutError( const FormulaToken& r ) +{ + if ( sp >= MAXSTACK ) + SetError( FormulaError::StackOverflow ); + else + { + r.IncRef(); + if( sp >= maxsp ) + maxsp = sp + 1; + else + pStack[ sp ]->DecRef(); + pStack[ sp ] = &r; + ++sp; + } +} + +void ScInterpreter::Push( const FormulaToken& r ) +{ + if ( sp >= MAXSTACK ) + SetError( FormulaError::StackOverflow ); + else + { + if (nGlobalError != FormulaError::NONE) + { + if (r.GetType() == svError) + PushWithoutError( r); + else + PushTempTokenWithoutError( new FormulaErrorToken( nGlobalError)); + } + else + PushWithoutError( r); + } +} + +void ScInterpreter::PushTempToken( FormulaToken* p ) +{ + if ( sp >= MAXSTACK ) + { + SetError( FormulaError::StackOverflow ); + // p may be a dangling pointer hereafter! + p->DeleteIfZeroRef(); + } + else + { + if (nGlobalError != FormulaError::NONE) + { + if (p->GetType() == svError) + { + p->SetError( nGlobalError); + PushTempTokenWithoutError( p); + } + else + { + // p may be a dangling pointer hereafter! + p->DeleteIfZeroRef(); + PushTempTokenWithoutError( new FormulaErrorToken( nGlobalError)); + } + } + else + PushTempTokenWithoutError( p); + } +} + +void ScInterpreter::PushTempTokenWithoutError( const FormulaToken* p ) +{ + p->IncRef(); + if ( sp >= MAXSTACK ) + { + SetError( FormulaError::StackOverflow ); + // p may be a dangling pointer hereafter! + p->DecRef(); + } + else + { + if( sp >= maxsp ) + maxsp = sp + 1; + else + pStack[ sp ]->DecRef(); + pStack[ sp ] = p; + ++sp; + } +} + +void ScInterpreter::PushTokenRef( const formula::FormulaConstTokenRef& x ) +{ + if ( sp >= MAXSTACK ) + { + SetError( FormulaError::StackOverflow ); + } + else + { + if (nGlobalError != FormulaError::NONE) + { + if (x->GetType() == svError && x->GetError() == nGlobalError) + PushTempTokenWithoutError( x.get()); + else + PushTempTokenWithoutError( new FormulaErrorToken( nGlobalError)); + } + else + PushTempTokenWithoutError( x.get()); + } +} + +void ScInterpreter::PushCellResultToken( bool bDisplayEmptyAsString, + const ScAddress & rAddress, SvNumFormatType * pRetTypeExpr, sal_uInt32 * pRetIndexExpr, bool bFinalResult ) +{ + ScRefCellValue aCell(mrDoc, rAddress); + if (aCell.hasEmptyValue()) + { + bool bInherited = (aCell.getType() == CELLTYPE_FORMULA); + if (pRetTypeExpr && pRetIndexExpr) + mrDoc.GetNumberFormatInfo(mrContext, *pRetTypeExpr, *pRetIndexExpr, rAddress); + PushTempToken( new ScEmptyCellToken( bInherited, bDisplayEmptyAsString)); + return; + } + + FormulaError nErr = FormulaError::NONE; + if (aCell.getType() == CELLTYPE_FORMULA) + nErr = aCell.getFormula()->GetErrCode(); + + if (nErr != FormulaError::NONE) + { + PushError( nErr); + if (pRetTypeExpr) + *pRetTypeExpr = SvNumFormatType::UNDEFINED; + if (pRetIndexExpr) + *pRetIndexExpr = 0; + } + else if (aCell.hasString()) + { + svl::SharedString aRes; + GetCellString( aRes, aCell); + PushString( aRes); + if (pRetTypeExpr) + *pRetTypeExpr = SvNumFormatType::TEXT; + if (pRetIndexExpr) + *pRetIndexExpr = 0; + } + else + { + double fVal = GetCellValue(rAddress, aCell); + if (bFinalResult) + { + TreatDoubleError( fVal); + if (!IfErrorPushError()) + PushTempTokenWithoutError( CreateFormulaDoubleToken( fVal)); + } + else + { + PushDouble( fVal); + } + if (pRetTypeExpr) + *pRetTypeExpr = nCurFmtType; + if (pRetIndexExpr) + *pRetIndexExpr = nCurFmtIndex; + } +} + +// Simply throw away TOS. + +void ScInterpreter::Pop() +{ + if( sp ) + sp--; + else + SetError(FormulaError::UnknownStackVariable); +} + +// Simply throw away TOS and set error code, used with ocIsError et al. + +void ScInterpreter::PopError() +{ + if( sp ) + { + sp--; + if (pStack[sp]->GetType() == svError) + nGlobalError = pStack[sp]->GetError(); + } + else + SetError(FormulaError::UnknownStackVariable); +} + +FormulaConstTokenRef ScInterpreter::PopToken() +{ + if (sp) + { + sp--; + const FormulaToken* p = pStack[ sp ]; + if (p->GetType() == svError) + nGlobalError = p->GetError(); + return p; + } + else + SetError(FormulaError::UnknownStackVariable); + return nullptr; +} + +double ScInterpreter::PopDouble() +{ + nCurFmtType = SvNumFormatType::NUMBER; + nCurFmtIndex = 0; + if( sp ) + { + --sp; + const FormulaToken* p = pStack[ sp ]; + switch (p->GetType()) + { + case svError: + nGlobalError = p->GetError(); + break; + case svDouble: + { + SvNumFormatType nType = static_cast<SvNumFormatType>(p->GetDoubleType()); + if (nType != SvNumFormatType::ALL && nType != SvNumFormatType::UNDEFINED) + nCurFmtType = nType; + return p->GetDouble(); + } + case svEmptyCell: + case svMissing: + return 0.0; + default: + SetError( FormulaError::IllegalArgument); + } + } + else + SetError( FormulaError::UnknownStackVariable); + return 0.0; +} + +const svl::SharedString & ScInterpreter::PopString() +{ + nCurFmtType = SvNumFormatType::TEXT; + nCurFmtIndex = 0; + if( sp ) + { + --sp; + const FormulaToken* p = pStack[ sp ]; + switch (p->GetType()) + { + case svError: + nGlobalError = p->GetError(); + break; + case svString: + return p->GetString(); + case svEmptyCell: + case svMissing: + return svl::SharedString::getEmptyString(); + default: + SetError( FormulaError::IllegalArgument); + } + } + else + SetError( FormulaError::UnknownStackVariable); + + return svl::SharedString::getEmptyString(); +} + +void ScInterpreter::ValidateRef( const ScSingleRefData & rRef ) +{ + SCCOL nCol; + SCROW nRow; + SCTAB nTab; + SingleRefToVars( rRef, nCol, nRow, nTab); +} + +void ScInterpreter::ValidateRef( const ScComplexRefData & rRef ) +{ + ValidateRef( rRef.Ref1); + ValidateRef( rRef.Ref2); +} + +void ScInterpreter::ValidateRef( const ScRefList & rRefList ) +{ + for (const auto& rRef : rRefList) + { + ValidateRef( rRef); + } +} + +void ScInterpreter::SingleRefToVars( const ScSingleRefData & rRef, + SCCOL & rCol, SCROW & rRow, SCTAB & rTab ) +{ + if ( rRef.IsColRel() ) + rCol = aPos.Col() + rRef.Col(); + else + rCol = rRef.Col(); + + if ( rRef.IsRowRel() ) + rRow = aPos.Row() + rRef.Row(); + else + rRow = rRef.Row(); + + if ( rRef.IsTabRel() ) + rTab = aPos.Tab() + rRef.Tab(); + else + rTab = rRef.Tab(); + + if( !mrDoc.ValidCol( rCol) || rRef.IsColDeleted() ) + { + SetError( FormulaError::NoRef ); + rCol = 0; + } + if( !mrDoc.ValidRow( rRow) || rRef.IsRowDeleted() ) + { + SetError( FormulaError::NoRef ); + rRow = 0; + } + if( !ValidTab( rTab, mrDoc.GetTableCount() - 1) || rRef.IsTabDeleted() ) + { + SetError( FormulaError::NoRef ); + rTab = 0; + } +} + +void ScInterpreter::PopSingleRef(SCCOL& rCol, SCROW &rRow, SCTAB& rTab) +{ + ScAddress aAddr(rCol, rRow, rTab); + PopSingleRef(aAddr); + rCol = aAddr.Col(); + rRow = aAddr.Row(); + rTab = aAddr.Tab(); +} + +void ScInterpreter::PopSingleRef( ScAddress& rAdr ) +{ + if( sp ) + { + --sp; + const FormulaToken* p = pStack[ sp ]; + switch (p->GetType()) + { + case svError: + nGlobalError = p->GetError(); + break; + case svSingleRef: + { + const ScSingleRefData* pRefData = p->GetSingleRef(); + if (pRefData->IsDeleted()) + { + SetError( FormulaError::NoRef); + break; + } + + SCCOL nCol; + SCROW nRow; + SCTAB nTab; + SingleRefToVars( *pRefData, nCol, nRow, nTab); + rAdr.Set( nCol, nRow, nTab ); + if (!mrDoc.m_TableOpList.empty()) + ReplaceCell( rAdr ); + } + break; + default: + SetError( FormulaError::IllegalParameter); + } + } + else + SetError( FormulaError::UnknownStackVariable); +} + +void ScInterpreter::DoubleRefToVars( const formula::FormulaToken* p, + SCCOL& rCol1, SCROW &rRow1, SCTAB& rTab1, + SCCOL& rCol2, SCROW &rRow2, SCTAB& rTab2 ) +{ + const ScComplexRefData& rCRef = *p->GetDoubleRef(); + SingleRefToVars( rCRef.Ref1, rCol1, rRow1, rTab1); + SingleRefToVars( rCRef.Ref2, rCol2, rRow2, rTab2); + PutInOrder(rCol1, rCol2); + PutInOrder(rRow1, rRow2); + PutInOrder(rTab1, rTab2); + if (!mrDoc.m_TableOpList.empty()) + { + ScRange aRange( rCol1, rRow1, rTab1, rCol2, rRow2, rTab2 ); + if ( IsTableOpInRange( aRange ) ) + SetError( FormulaError::IllegalParameter ); + } +} + +ScDBRangeBase* ScInterpreter::PopDBDoubleRef() +{ + StackVar eType = GetStackType(); + switch (eType) + { + case svUnknown: + SetError(FormulaError::UnknownStackVariable); + break; + case svError: + PopError(); + break; + case svDoubleRef: + { + SCCOL nCol1, nCol2; + SCROW nRow1, nRow2; + SCTAB nTab1, nTab2; + PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + if (nGlobalError != FormulaError::NONE) + break; + return new ScDBInternalRange(&mrDoc, + ScRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2)); + } + case svMatrix: + case svExternalDoubleRef: + { + ScMatrixRef pMat; + if (eType == svMatrix) + pMat = PopMatrix(); + else + PopExternalDoubleRef(pMat); + if (nGlobalError != FormulaError::NONE) + break; + return new ScDBExternalRange(&mrDoc, std::move(pMat)); + } + default: + SetError( FormulaError::IllegalParameter); + } + + return nullptr; +} + +void ScInterpreter::PopDoubleRef(SCCOL& rCol1, SCROW &rRow1, SCTAB& rTab1, + SCCOL& rCol2, SCROW &rRow2, SCTAB& rTab2) +{ + if( sp ) + { + --sp; + const FormulaToken* p = pStack[ sp ]; + switch (p->GetType()) + { + case svError: + nGlobalError = p->GetError(); + break; + case svDoubleRef: + DoubleRefToVars( p, rCol1, rRow1, rTab1, rCol2, rRow2, rTab2); + break; + default: + SetError( FormulaError::IllegalParameter); + } + } + else + SetError( FormulaError::UnknownStackVariable); +} + +void ScInterpreter::DoubleRefToRange( const ScComplexRefData & rCRef, + ScRange & rRange, bool bDontCheckForTableOp ) +{ + SCCOL nCol; + SCROW nRow; + SCTAB nTab; + SingleRefToVars( rCRef.Ref1, nCol, nRow, nTab); + rRange.aStart.Set( nCol, nRow, nTab ); + SingleRefToVars( rCRef.Ref2, nCol, nRow, nTab); + rRange.aEnd.Set( nCol, nRow, nTab ); + rRange.PutInOrder(); + if (!mrDoc.m_TableOpList.empty() && !bDontCheckForTableOp) + { + if ( IsTableOpInRange( rRange ) ) + SetError( FormulaError::IllegalParameter ); + } +} + +void ScInterpreter::PopDoubleRef( ScRange & rRange, short & rParam, size_t & rRefInList ) +{ + if (sp) + { + const formula::FormulaToken* pToken = pStack[ sp-1 ]; + switch (pToken->GetType()) + { + case svError: + nGlobalError = pToken->GetError(); + break; + case svDoubleRef: + { + --sp; + const ScComplexRefData* pRefData = pToken->GetDoubleRef(); + if (pRefData->IsDeleted()) + { + SetError( FormulaError::NoRef); + break; + } + DoubleRefToRange( *pRefData, rRange); + break; + } + case svRefList: + { + const ScRefList* pList = pToken->GetRefList(); + if (rRefInList < pList->size()) + { + DoubleRefToRange( (*pList)[rRefInList], rRange); + if (++rRefInList < pList->size()) + ++rParam; + else + { + --sp; + rRefInList = 0; + } + } + else + { + --sp; + rRefInList = 0; + SetError( FormulaError::IllegalParameter); + } + } + break; + default: + SetError( FormulaError::IllegalParameter); + } + } + else + SetError( FormulaError::UnknownStackVariable); +} + +void ScInterpreter::PopDoubleRef( ScRange& rRange, bool bDontCheckForTableOp ) +{ + if( sp ) + { + --sp; + const FormulaToken* p = pStack[ sp ]; + switch (p->GetType()) + { + case svError: + nGlobalError = p->GetError(); + break; + case svDoubleRef: + DoubleRefToRange( *p->GetDoubleRef(), rRange, bDontCheckForTableOp); + break; + default: + SetError( FormulaError::IllegalParameter); + } + } + else + SetError( FormulaError::UnknownStackVariable); +} + +const ScComplexRefData* ScInterpreter::GetStackDoubleRef(size_t rRefInList) +{ + if( sp ) + { + const FormulaToken* p = pStack[ sp - 1 ]; + switch (p->GetType()) + { + case svDoubleRef: + return p->GetDoubleRef(); + case svRefList: + { + const ScRefList* pList = p->GetRefList(); + if (rRefInList < pList->size()) + return &(*pList)[rRefInList]; + break; + } + default: + break; + } + } + return nullptr; +} + +void ScInterpreter::PopExternalSingleRef(sal_uInt16& rFileId, OUString& rTabName, ScSingleRefData& rRef) +{ + if (!sp) + { + SetError(FormulaError::UnknownStackVariable); + return; + } + + --sp; + const FormulaToken* p = pStack[sp]; + StackVar eType = p->GetType(); + + if (eType == svError) + { + nGlobalError = p->GetError(); + return; + } + + if (eType != svExternalSingleRef) + { + SetError( FormulaError::IllegalParameter); + return; + } + + rFileId = p->GetIndex(); + rTabName = p->GetString().getString(); + rRef = *p->GetSingleRef(); +} + +void ScInterpreter::PopExternalSingleRef(ScExternalRefCache::TokenRef& rToken, ScExternalRefCache::CellFormat* pFmt) +{ + sal_uInt16 nFileId; + OUString aTabName; + ScSingleRefData aData; + PopExternalSingleRef(nFileId, aTabName, aData, rToken, pFmt); +} + +void ScInterpreter::PopExternalSingleRef( + sal_uInt16& rFileId, OUString& rTabName, ScSingleRefData& rRef, + ScExternalRefCache::TokenRef& rToken, ScExternalRefCache::CellFormat* pFmt) +{ + PopExternalSingleRef(rFileId, rTabName, rRef); + if (nGlobalError != FormulaError::NONE) + return; + + ScExternalRefManager* pRefMgr = mrDoc.GetExternalRefManager(); + const OUString* pFile = pRefMgr->getExternalFileName(rFileId); + if (!pFile) + { + SetError(FormulaError::NoName); + return; + } + + if (rRef.IsTabRel()) + { + OSL_FAIL("ScCompiler::GetToken: external single reference must have an absolute table reference!"); + SetError(FormulaError::NoRef); + return; + } + + ScAddress aAddr = rRef.toAbs(mrDoc, aPos); + ScExternalRefCache::CellFormat aFmt; + ScExternalRefCache::TokenRef xNew = pRefMgr->getSingleRefToken( + rFileId, rTabName, aAddr, &aPos, nullptr, &aFmt); + + if (!xNew) + { + SetError(FormulaError::NoRef); + return; + } + + if (xNew->GetType() == svError) + SetError( xNew->GetError()); + + rToken = xNew; + if (pFmt) + *pFmt = aFmt; +} + +void ScInterpreter::PopExternalDoubleRef(sal_uInt16& rFileId, OUString& rTabName, ScComplexRefData& rRef) +{ + if (!sp) + { + SetError(FormulaError::UnknownStackVariable); + return; + } + + --sp; + const FormulaToken* p = pStack[sp]; + StackVar eType = p->GetType(); + + if (eType == svError) + { + nGlobalError = p->GetError(); + return; + } + + if (eType != svExternalDoubleRef) + { + SetError( FormulaError::IllegalParameter); + return; + } + + rFileId = p->GetIndex(); + rTabName = p->GetString().getString(); + rRef = *p->GetDoubleRef(); +} + +void ScInterpreter::PopExternalDoubleRef(ScExternalRefCache::TokenArrayRef& rArray) +{ + sal_uInt16 nFileId; + OUString aTabName; + ScComplexRefData aData; + PopExternalDoubleRef(nFileId, aTabName, aData); + if (nGlobalError != FormulaError::NONE) + return; + + GetExternalDoubleRef(nFileId, aTabName, aData, rArray); + if (nGlobalError != FormulaError::NONE) + return; +} + +void ScInterpreter::PopExternalDoubleRef(ScMatrixRef& rMat) +{ + ScExternalRefCache::TokenArrayRef pArray; + PopExternalDoubleRef(pArray); + if (nGlobalError != FormulaError::NONE) + return; + + // For now, we only support single range data for external + // references, which means the array should only contain a + // single matrix token. + formula::FormulaToken* p = pArray->FirstToken(); + if (!p || p->GetType() != svMatrix) + SetError( FormulaError::IllegalParameter); + else + { + rMat = p->GetMatrix(); + if (!rMat) + SetError( FormulaError::UnknownVariable); + } +} + +void ScInterpreter::GetExternalDoubleRef( + sal_uInt16 nFileId, const OUString& rTabName, const ScComplexRefData& rData, ScExternalRefCache::TokenArrayRef& rArray) +{ + ScExternalRefManager* pRefMgr = mrDoc.GetExternalRefManager(); + const OUString* pFile = pRefMgr->getExternalFileName(nFileId); + if (!pFile) + { + SetError(FormulaError::NoName); + return; + } + if (rData.Ref1.IsTabRel() || rData.Ref2.IsTabRel()) + { + OSL_FAIL("ScCompiler::GetToken: external double reference must have an absolute table reference!"); + SetError(FormulaError::NoRef); + return; + } + + ScComplexRefData aData(rData); + ScRange aRange = aData.toAbs(mrDoc, aPos); + if (!mrDoc.ValidColRow(aRange.aStart.Col(), aRange.aStart.Row()) || !mrDoc.ValidColRow(aRange.aEnd.Col(), aRange.aEnd.Row())) + { + SetError(FormulaError::NoRef); + return; + } + + ScExternalRefCache::TokenArrayRef pArray = pRefMgr->getDoubleRefTokens( + nFileId, rTabName, aRange, &aPos); + + if (!pArray) + { + SetError(FormulaError::IllegalArgument); + return; + } + + formula::FormulaTokenArrayPlainIterator aIter(*pArray); + formula::FormulaToken* pToken = aIter.First(); + assert(pToken); + if (pToken->GetType() == svError) + { + SetError( pToken->GetError()); + return; + } + if (pToken->GetType() != svMatrix) + { + SetError(FormulaError::IllegalArgument); + return; + } + + if (aIter.Next()) + { + // Can't handle more than one matrix per parameter. + SetError( FormulaError::IllegalArgument); + return; + } + + rArray = pArray; +} + +bool ScInterpreter::PopDoubleRefOrSingleRef( ScAddress& rAdr ) +{ + switch ( GetStackType() ) + { + case svDoubleRef : + { + ScRange aRange; + PopDoubleRef( aRange, true ); + return DoubleRefToPosSingleRef( aRange, rAdr ); + } + case svSingleRef : + { + PopSingleRef( rAdr ); + return true; + } + default: + PopError(); + SetError( FormulaError::NoRef ); + } + return false; +} + +void ScInterpreter::PopDoubleRefPushMatrix() +{ + if ( GetStackType() == svDoubleRef ) + { + ScMatrixRef pMat = GetMatrix(); + if ( pMat ) + PushMatrix( pMat ); + else + PushIllegalParameter(); + } + else + SetError( FormulaError::NoRef ); +} + +void ScInterpreter::PopRefListPushMatrixOrRef() +{ + if ( GetStackType() == svRefList ) + { + FormulaConstTokenRef xTok = pStack[sp-1]; + const std::vector<ScComplexRefData>* pv = xTok->GetRefList(); + if (pv) + { + const size_t nEntries = pv->size(); + if (nEntries == 1) + { + --sp; + PushTempTokenWithoutError( new ScDoubleRefToken( mrDoc.GetSheetLimits(), (*pv)[0] )); + } + else if (bMatrixFormula) + { + // Only single cells can be stuffed into a column vector. + // XXX NOTE: Excel doesn't do this but returns #VALUE! instead. + // Though there's no compelling reason not to... + for (const auto & rRef : *pv) + { + if (rRef.Ref1 != rRef.Ref2) + return; + } + ScMatrixRef xMat = GetNewMat( 1, nEntries, true); // init empty + if (!xMat) + return; + for (size_t i=0; i < nEntries; ++i) + { + SCCOL nCol; SCROW nRow; SCTAB nTab; + SingleRefToVars( (*pv)[i].Ref1, nCol, nRow, nTab); + if (nGlobalError == FormulaError::NONE) + { + ScAddress aAdr( nCol, nRow, nTab); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasError()) + xMat->PutError( aCell.getFormula()->GetErrCode(), 0, i); + else if (aCell.hasEmptyValue()) + xMat->PutEmpty( 0, i); + else if (aCell.hasString()) + xMat->PutString( mrStrPool.intern( aCell.getString(&mrDoc)), 0, i); + else + xMat->PutDouble( aCell.getValue(), 0, i); + } + else + { + xMat->PutError( nGlobalError, 0, i); + nGlobalError = FormulaError::NONE; + } + } + --sp; + PushMatrix( xMat); + } + } + // else: keep token on stack, something will handle the error + } + else + SetError( FormulaError::NoRef ); +} + +void ScInterpreter::ConvertMatrixJumpConditionToMatrix() +{ + StackVar eStackType = GetStackType(); + if (eStackType == svUnknown) + return; // can't do anything, some caller will catch that + if (eStackType == svMatrix) + return; // already matrix, nothing to do + + if (eStackType != svDoubleRef && GetStackType(2) != svJumpMatrix) + return; // always convert svDoubleRef, others only in JumpMatrix context + + GetTokenMatrixMap(); // make sure it exists, create if not. + ScMatrixRef pMat = GetMatrix(); + if ( pMat ) + PushMatrix( pMat ); + else + PushIllegalParameter(); +} + +bool ScInterpreter::ConvertMatrixParameters() +{ + sal_uInt16 nParams = pCur->GetParamCount(); + SAL_WARN_IF( nParams > sp, "sc.core", "ConvertMatrixParameters: stack/param count mismatch: eOp: " + << static_cast<int>(pCur->GetOpCode()) << " sp: " << sp << " nParams: " << nParams); + assert(nParams <= sp); + SCSIZE nJumpCols = 0, nJumpRows = 0; + for ( sal_uInt16 i=1; i <= nParams && i <= sp; ++i ) + { + const FormulaToken* p = pStack[ sp - i ]; + if ( p->GetOpCode() != ocPush && p->GetOpCode() != ocMissing) + { + assert(!"ConvertMatrixParameters: not a push"); + } + else + { + switch ( p->GetType() ) + { + case svDouble: + case svString: + case svSingleRef: + case svExternalSingleRef: + case svMissing: + case svError: + case svEmptyCell: + // nothing to do + break; + case svMatrix: + { + if ( ScParameterClassification::GetParameterType( pCur, nParams - i) + == formula::ParamClass::Value ) + { // only if single value expected + ScConstMatrixRef pMat = p->GetMatrix(); + if ( !pMat ) + SetError( FormulaError::UnknownVariable); + else + { + SCSIZE nCols, nRows; + pMat->GetDimensions( nCols, nRows); + if ( nJumpCols < nCols ) + nJumpCols = nCols; + if ( nJumpRows < nRows ) + nJumpRows = nRows; + } + } + } + break; + case svDoubleRef: + { + formula::ParamClass eType = ScParameterClassification::GetParameterType( pCur, nParams - i); + if ( eType != formula::ParamClass::Reference && + eType != formula::ParamClass::ReferenceOrRefArray && + eType != formula::ParamClass::ReferenceOrForceArray && + // For scalar Value: convert to Array/JumpMatrix + // only if in array formula context, else (function + // has ForceArray or ReferenceOrForceArray + // parameter *somewhere else*) pick a normal + // position dependent implicit intersection later. + (eType != formula::ParamClass::Value || IsInArrayContext())) + { + SCCOL nCol1, nCol2; + SCROW nRow1, nRow2; + SCTAB nTab1, nTab2; + DoubleRefToVars( p, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + // Make sure the map exists, created if not. + GetTokenMatrixMap(); + ScMatrixRef pMat = CreateMatrixFromDoubleRef( p, + nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + if (pMat) + { + if ( eType == formula::ParamClass::Value ) + { // only if single value expected + if ( nJumpCols < o3tl::make_unsigned(nCol2 - nCol1 + 1) ) + nJumpCols = static_cast<SCSIZE>(nCol2 - nCol1 + 1); + if ( nJumpRows < o3tl::make_unsigned(nRow2 - nRow1 + 1) ) + nJumpRows = static_cast<SCSIZE>(nRow2 - nRow1 + 1); + } + formula::FormulaToken* pNew = new ScMatrixToken( std::move(pMat) ); + pNew->IncRef(); + pStack[ sp - i ] = pNew; + p->DecRef(); // p may be dead now! + } + } + } + break; + case svExternalDoubleRef: + { + formula::ParamClass eType = ScParameterClassification::GetParameterType( pCur, nParams - i); + if (eType == formula::ParamClass::Value || eType == formula::ParamClass::Array) + { + sal_uInt16 nFileId = p->GetIndex(); + OUString aTabName = p->GetString().getString(); + const ScComplexRefData& rRef = *p->GetDoubleRef(); + ScExternalRefCache::TokenArrayRef pArray; + GetExternalDoubleRef(nFileId, aTabName, rRef, pArray); + if (nGlobalError != FormulaError::NONE || !pArray) + break; + formula::FormulaToken* pTemp = pArray->FirstToken(); + if (!pTemp) + break; + + ScMatrixRef pMat = pTemp->GetMatrix(); + if (pMat) + { + if (eType == formula::ParamClass::Value) + { // only if single value expected + SCSIZE nC, nR; + pMat->GetDimensions( nC, nR); + if (nJumpCols < nC) + nJumpCols = nC; + if (nJumpRows < nR) + nJumpRows = nR; + } + formula::FormulaToken* pNew = new ScMatrixToken( std::move(pMat) ); + pNew->IncRef(); + pStack[ sp - i ] = pNew; + p->DecRef(); // p may be dead now! + } + } + } + break; + case svRefList: + { + formula::ParamClass eType = ScParameterClassification::GetParameterType( pCur, nParams - i); + if ( eType != formula::ParamClass::Reference && + eType != formula::ParamClass::ReferenceOrRefArray && + eType != formula::ParamClass::ReferenceOrForceArray && + eType != formula::ParamClass::ForceArray) + { + // can't convert to matrix + SetError( FormulaError::NoRef); + } + // else: the consuming function has to decide if and how to + // handle a reference list argument in array context. + } + break; + default: + assert(!"ConvertMatrixParameters: unknown parameter type"); + } + } + } + if( nJumpCols && nJumpRows ) + { + short nPC = aCode.GetPC(); + short nStart = nPC - 1; // restart on current code (-1) + short nNext = nPC; // next instruction after subroutine + short nStop = nPC + 1; // stop subroutine before reaching that + FormulaConstTokenRef xNew; + ScTokenMatrixMap::const_iterator aMapIter; + if ((aMapIter = maTokenMatrixMap.find( pCur)) != maTokenMatrixMap.end()) + xNew = (*aMapIter).second; + else + { + std::shared_ptr<ScJumpMatrix> pJumpMat; + try + { + pJumpMat = std::make_shared<ScJumpMatrix>( pCur->GetOpCode(), nJumpCols, nJumpRows); + } + catch (const std::bad_alloc&) + { + SAL_WARN("sc.core", "std::bad_alloc in ScJumpMatrix ctor with " << nJumpCols << " columns and " << nJumpRows << " rows"); + return false; + } + pJumpMat->SetAllJumps( 1.0, nStart, nNext, nStop); + // pop parameters and store in ScJumpMatrix, push in JumpMatrix() + ScTokenVec aParams(nParams); + for ( sal_uInt16 i=1; i <= nParams && sp > 0; ++i ) + { + const FormulaToken* p = pStack[ --sp ]; + p->IncRef(); + // store in reverse order such that a push may simply iterate + aParams[ nParams - i ] = p; + } + pJumpMat->SetJumpParameters( std::move(aParams) ); + xNew = new ScJumpMatrixToken( std::move(pJumpMat) ); + GetTokenMatrixMap().emplace(pCur, xNew); + } + PushTempTokenWithoutError( xNew.get()); + // set continuation point of path for main code line + aCode.Jump( nNext, nNext); + return true; + } + return false; +} + +ScMatrixRef ScInterpreter::PopMatrix() +{ + if( sp ) + { + --sp; + const FormulaToken* p = pStack[ sp ]; + switch (p->GetType()) + { + case svError: + nGlobalError = p->GetError(); + break; + case svMatrix: + { + // ScMatrix itself maintains an im/mutable flag that should + // be obeyed where necessary... so we can return ScMatrixRef + // here instead of ScConstMatrixRef. + ScMatrix* pMat = const_cast<FormulaToken*>(p)->GetMatrix(); + if ( pMat ) + pMat->SetErrorInterpreter( this); + else + SetError( FormulaError::UnknownVariable); + return pMat; + } + default: + SetError( FormulaError::IllegalParameter); + } + } + else + SetError( FormulaError::UnknownStackVariable); + return nullptr; +} + +sc::RangeMatrix ScInterpreter::PopRangeMatrix() +{ + sc::RangeMatrix aRet; + if (sp) + { + switch (pStack[sp-1]->GetType()) + { + case svMatrix: + { + --sp; + const FormulaToken* p = pStack[sp]; + aRet.mpMat = const_cast<FormulaToken*>(p)->GetMatrix(); + if (aRet.mpMat) + { + aRet.mpMat->SetErrorInterpreter(this); + if (p->GetByte() == MATRIX_TOKEN_HAS_RANGE) + { + const ScComplexRefData& rRef = *p->GetDoubleRef(); + if (!rRef.Ref1.IsColRel() && !rRef.Ref1.IsRowRel() && !rRef.Ref2.IsColRel() && !rRef.Ref2.IsRowRel()) + { + aRet.mnCol1 = rRef.Ref1.Col(); + aRet.mnRow1 = rRef.Ref1.Row(); + aRet.mnTab1 = rRef.Ref1.Tab(); + aRet.mnCol2 = rRef.Ref2.Col(); + aRet.mnRow2 = rRef.Ref2.Row(); + aRet.mnTab2 = rRef.Ref2.Tab(); + } + } + } + else + SetError( FormulaError::UnknownVariable); + } + break; + default: + aRet.mpMat = PopMatrix(); + } + } + return aRet; +} + +void ScInterpreter::QueryMatrixType(const ScMatrixRef& xMat, SvNumFormatType& rRetTypeExpr, sal_uInt32& rRetIndexExpr) +{ + if (xMat) + { + SCSIZE nCols, nRows; + xMat->GetDimensions(nCols, nRows); + ScMatrixValue nMatVal = xMat->Get(0, 0); + ScMatValType nMatValType = nMatVal.nType; + if (ScMatrix::IsNonValueType( nMatValType)) + { + if ( xMat->IsEmptyPath( 0, 0)) + { // result of empty FALSE jump path + FormulaTokenRef xRes = CreateFormulaDoubleToken( 0.0); + PushTempToken( new ScMatrixFormulaCellToken(nCols, nRows, xMat, xRes.get())); + rRetTypeExpr = SvNumFormatType::LOGICAL; + } + else if ( xMat->IsEmptyResult( 0, 0)) + { // empty formula result + FormulaTokenRef xRes = new ScEmptyCellToken( true, true); // inherited, display empty + PushTempToken( new ScMatrixFormulaCellToken(nCols, nRows, xMat, xRes.get())); + } + else if ( xMat->IsEmpty( 0, 0)) + { // empty or empty cell + FormulaTokenRef xRes = new ScEmptyCellToken( false, true); // not inherited, display empty + PushTempToken( new ScMatrixFormulaCellToken(nCols, nRows, xMat, xRes.get())); + } + else + { + FormulaTokenRef xRes = new FormulaStringToken( nMatVal.GetString() ); + PushTempToken( new ScMatrixFormulaCellToken(nCols, nRows, xMat, xRes.get())); + rRetTypeExpr = SvNumFormatType::TEXT; + } + } + else + { + FormulaError nErr = GetDoubleErrorValue( nMatVal.fVal); + FormulaTokenRef xRes; + if (nErr != FormulaError::NONE) + xRes = new FormulaErrorToken( nErr); + else + xRes = CreateFormulaDoubleToken( nMatVal.fVal); + PushTempToken( new ScMatrixFormulaCellToken(nCols, nRows, xMat, xRes.get())); + if ( rRetTypeExpr != SvNumFormatType::LOGICAL ) + rRetTypeExpr = SvNumFormatType::NUMBER; + } + rRetIndexExpr = 0; + xMat->SetErrorInterpreter( nullptr); + } + else + SetError( FormulaError::UnknownStackVariable); +} + +formula::FormulaToken* ScInterpreter::CreateFormulaDoubleToken( double fVal, SvNumFormatType nFmt ) +{ + assert( mrContext.maTokens.size() == TOKEN_CACHE_SIZE ); + + // Find a spare token + for ( auto p : mrContext.maTokens ) + { + if (p && p->GetRef() == 1) + { + p->GetDoubleAsReference() = fVal; + p->SetDoubleType( static_cast<sal_Int16>(nFmt) ); + return p; + } + } + + // Allocate a new token + auto p = new FormulaTypedDoubleToken( fVal, static_cast<sal_Int16>(nFmt) ); + if ( mrContext.maTokens[mrContext.mnTokenCachePos] ) + mrContext.maTokens[mrContext.mnTokenCachePos]->DecRef(); + mrContext.maTokens[mrContext.mnTokenCachePos] = p; + p->IncRef(); + mrContext.mnTokenCachePos = (mrContext.mnTokenCachePos + 1) % TOKEN_CACHE_SIZE; + return p; +} + +formula::FormulaToken* ScInterpreter::CreateDoubleOrTypedToken( double fVal ) +{ + // NumberFormat::NUMBER is the default untyped double. + if (nFuncFmtType != SvNumFormatType::ALL && nFuncFmtType != SvNumFormatType::NUMBER && + nFuncFmtType != SvNumFormatType::UNDEFINED) + return CreateFormulaDoubleToken( fVal, nFuncFmtType); + else + return CreateFormulaDoubleToken( fVal); +} + +void ScInterpreter::PushDouble(double nVal) +{ + TreatDoubleError( nVal ); + if (!IfErrorPushError()) + PushTempTokenWithoutError( CreateDoubleOrTypedToken( nVal)); +} + +void ScInterpreter::PushInt(int nVal) +{ + if (!IfErrorPushError()) + PushTempTokenWithoutError( CreateDoubleOrTypedToken( nVal)); +} + +void ScInterpreter::PushStringBuffer( const sal_Unicode* pString ) +{ + if ( pString ) + { + svl::SharedString aSS = mrDoc.GetSharedStringPool().intern(OUString(pString)); + PushString(aSS); + } + else + PushString(svl::SharedString::getEmptyString()); +} + +void ScInterpreter::PushString( const OUString& rStr ) +{ + PushString(mrDoc.GetSharedStringPool().intern(rStr)); +} + +void ScInterpreter::PushString( const svl::SharedString& rString ) +{ + if (!IfErrorPushError()) + PushTempTokenWithoutError( new FormulaStringToken( rString ) ); +} + +void ScInterpreter::PushSingleRef(SCCOL nCol, SCROW nRow, SCTAB nTab) +{ + if (!IfErrorPushError()) + { + ScSingleRefData aRef; + aRef.InitAddress(ScAddress(nCol,nRow,nTab)); + PushTempTokenWithoutError( new ScSingleRefToken( mrDoc.GetSheetLimits(), aRef ) ); + } +} + +void ScInterpreter::PushDoubleRef(SCCOL nCol1, SCROW nRow1, SCTAB nTab1, + SCCOL nCol2, SCROW nRow2, SCTAB nTab2) +{ + if (!IfErrorPushError()) + { + ScComplexRefData aRef; + aRef.InitRange(ScRange(nCol1,nRow1,nTab1,nCol2,nRow2,nTab2)); + PushTempTokenWithoutError( new ScDoubleRefToken( mrDoc.GetSheetLimits(), aRef ) ); + } +} + +void ScInterpreter::PushExternalSingleRef( + sal_uInt16 nFileId, const OUString& rTabName, SCCOL nCol, SCROW nRow, SCTAB nTab) +{ + if (!IfErrorPushError()) + { + ScSingleRefData aRef; + aRef.InitAddress(ScAddress(nCol,nRow,nTab)); + PushTempTokenWithoutError( new ScExternalSingleRefToken(nFileId, + mrDoc.GetSharedStringPool().intern( rTabName), aRef)) ; + } +} + +void ScInterpreter::PushExternalDoubleRef( + sal_uInt16 nFileId, const OUString& rTabName, + SCCOL nCol1, SCROW nRow1, SCTAB nTab1, SCCOL nCol2, SCROW nRow2, SCTAB nTab2) +{ + if (!IfErrorPushError()) + { + ScComplexRefData aRef; + aRef.InitRange(ScRange(nCol1,nRow1,nTab1,nCol2,nRow2,nTab2)); + PushTempTokenWithoutError( new ScExternalDoubleRefToken(nFileId, + mrDoc.GetSharedStringPool().intern( rTabName), aRef) ); + } +} + +void ScInterpreter::PushSingleRef( const ScRefAddress& rRef ) +{ + if (!IfErrorPushError()) + { + ScSingleRefData aRef; + aRef.InitFromRefAddress( mrDoc, rRef, aPos); + PushTempTokenWithoutError( new ScSingleRefToken( mrDoc.GetSheetLimits(), aRef ) ); + } +} + +void ScInterpreter::PushDoubleRef( const ScRefAddress& rRef1, const ScRefAddress& rRef2 ) +{ + if (!IfErrorPushError()) + { + ScComplexRefData aRef; + aRef.InitFromRefAddresses( mrDoc, rRef1, rRef2, aPos); + PushTempTokenWithoutError( new ScDoubleRefToken( mrDoc.GetSheetLimits(), aRef ) ); + } +} + +void ScInterpreter::PushMatrix( const sc::RangeMatrix& rMat ) +{ + if (!rMat.isRangeValid()) + { + // Just push the matrix part only. + PushMatrix(rMat.mpMat); + return; + } + + rMat.mpMat->SetErrorInterpreter(nullptr); + nGlobalError = FormulaError::NONE; + PushTempTokenWithoutError(new ScMatrixRangeToken(rMat)); +} + +void ScInterpreter::PushMatrix(const ScMatrixRef& pMat) +{ + pMat->SetErrorInterpreter( nullptr); + // No if (!IfErrorPushError()) because ScMatrix stores errors itself, + // but with notifying ScInterpreter via nGlobalError, substituting it would + // mean to inherit the error on all array elements in all following + // operations. + nGlobalError = FormulaError::NONE; + PushTempTokenWithoutError( new ScMatrixToken( pMat ) ); +} + +void ScInterpreter::PushError( FormulaError nError ) +{ + SetError( nError ); // only sets error if not already set + PushTempTokenWithoutError( new FormulaErrorToken( nGlobalError)); +} + +void ScInterpreter::PushParameterExpected() +{ + PushError( FormulaError::ParameterExpected); +} + +void ScInterpreter::PushIllegalParameter() +{ + PushError( FormulaError::IllegalParameter); +} + +void ScInterpreter::PushIllegalArgument() +{ + PushError( FormulaError::IllegalArgument); +} + +void ScInterpreter::PushNA() +{ + PushError( FormulaError::NotAvailable); +} + +void ScInterpreter::PushNoValue() +{ + PushError( FormulaError::NoValue); +} + +bool ScInterpreter::IsMissing() const +{ + return sp && pStack[sp - 1]->GetType() == svMissing; +} + +StackVar ScInterpreter::GetRawStackType() +{ + StackVar eRes; + if( sp ) + { + eRes = pStack[sp - 1]->GetType(); + } + else + { + SetError(FormulaError::UnknownStackVariable); + eRes = svUnknown; + } + return eRes; +} + +StackVar ScInterpreter::GetStackType() +{ + StackVar eRes; + if( sp ) + { + eRes = pStack[sp - 1]->GetType(); + if( eRes == svMissing || eRes == svEmptyCell ) + eRes = svDouble; // default! + } + else + { + SetError(FormulaError::UnknownStackVariable); + eRes = svUnknown; + } + return eRes; +} + +StackVar ScInterpreter::GetStackType( sal_uInt8 nParam ) +{ + StackVar eRes; + if( sp > nParam-1 ) + { + eRes = pStack[sp - nParam]->GetType(); + if( eRes == svMissing || eRes == svEmptyCell ) + eRes = svDouble; // default! + } + else + eRes = svUnknown; + return eRes; +} + +void ScInterpreter::ReverseStack( sal_uInt8 nParamCount ) +{ + //reverse order of parameter stack + assert( sp >= nParamCount && " less stack elements than parameters"); + sal_uInt16 nStackParams = std::min<sal_uInt16>( sp, nParamCount); + std::reverse( pStack+(sp-nStackParams), pStack+sp ); +} + +bool ScInterpreter::DoubleRefToPosSingleRef( const ScRange& rRange, ScAddress& rAdr ) +{ + // Check for a singleton first - no implicit intersection for them. + if( rRange.aStart == rRange.aEnd ) + { + rAdr = rRange.aStart; + return true; + } + + bool bOk = false; + + if ( pJumpMatrix ) + { + bOk = rRange.aStart.Tab() == rRange.aEnd.Tab(); + if ( !bOk ) + SetError( FormulaError::IllegalArgument); + else + { + SCSIZE nC, nR; + pJumpMatrix->GetPos( nC, nR); + rAdr.SetCol( sal::static_int_cast<SCCOL>( rRange.aStart.Col() + nC ) ); + rAdr.SetRow( sal::static_int_cast<SCROW>( rRange.aStart.Row() + nR ) ); + rAdr.SetTab( rRange.aStart.Tab()); + bOk = rRange.aStart.Col() <= rAdr.Col() && rAdr.Col() <= + rRange.aEnd.Col() && rRange.aStart.Row() <= rAdr.Row() && + rAdr.Row() <= rRange.aEnd.Row(); + if ( !bOk ) + SetError( FormulaError::NoValue); + } + return bOk; + } + + bOk = ScCompiler::DoubleRefToPosSingleRefScalarCase(rRange, rAdr, aPos); + + if ( !bOk ) + SetError( FormulaError::NoValue ); + return bOk; +} + +double ScInterpreter::GetDoubleFromMatrix(const ScMatrixRef& pMat) +{ + if (!pMat) + return 0.0; + + if ( !pJumpMatrix ) + { + double fVal = pMat->GetDoubleWithStringConversion( 0, 0); + FormulaError nErr = GetDoubleErrorValue( fVal); + if (nErr != FormulaError::NONE) + { + // Do not propagate the coded double error, but set nGlobalError in + // case the matrix did not have an error interpreter set. + SetError( nErr); + fVal = 0.0; + } + return fVal; + } + + SCSIZE nCols, nRows, nC, nR; + pMat->GetDimensions( nCols, nRows); + pJumpMatrix->GetPos( nC, nR); + // Use vector replication for single row/column arrays. + if ( (nC < nCols || nCols == 1) && (nR < nRows || nRows == 1) ) + { + double fVal = pMat->GetDoubleWithStringConversion( nC, nR); + FormulaError nErr = GetDoubleErrorValue( fVal); + if (nErr != FormulaError::NONE) + { + // Do not propagate the coded double error, but set nGlobalError in + // case the matrix did not have an error interpreter set. + SetError( nErr); + fVal = 0.0; + } + return fVal; + } + + SetError( FormulaError::NoValue); + return 0.0; +} + +double ScInterpreter::GetDouble() +{ + double nVal(0.0); + switch( GetRawStackType() ) + { + case svDouble: + nVal = PopDouble(); + break; + case svString: + nVal = ConvertStringToValue( PopString().getString()); + break; + case svSingleRef: + { + ScAddress aAdr; + PopSingleRef( aAdr ); + ScRefCellValue aCell(mrDoc, aAdr); + nVal = GetCellValue(aAdr, aCell); + } + break; + case svDoubleRef: + { // generate position dependent SingleRef + ScRange aRange; + PopDoubleRef( aRange ); + ScAddress aAdr; + if ( nGlobalError == FormulaError::NONE && DoubleRefToPosSingleRef( aRange, aAdr ) ) + { + ScRefCellValue aCell(mrDoc, aAdr); + nVal = GetCellValue(aAdr, aCell); + } + else + nVal = 0.0; + } + break; + case svExternalSingleRef: + { + ScExternalRefCache::TokenRef pToken; + PopExternalSingleRef(pToken); + if (nGlobalError == FormulaError::NONE) + { + if (pToken->GetType() == svDouble || pToken->GetType() == svEmptyCell) + nVal = pToken->GetDouble(); + else + nVal = ConvertStringToValue( pToken->GetString().getString()); + } + } + break; + case svExternalDoubleRef: + { + ScMatrixRef pMat; + PopExternalDoubleRef(pMat); + if (nGlobalError != FormulaError::NONE) + break; + + nVal = GetDoubleFromMatrix(pMat); + } + break; + case svMatrix: + { + ScMatrixRef pMat = PopMatrix(); + nVal = GetDoubleFromMatrix(pMat); + } + break; + case svError: + PopError(); + nVal = 0.0; + break; + case svEmptyCell: + case svMissing: + Pop(); + nVal = 0.0; + break; + default: + PopError(); + SetError( FormulaError::IllegalParameter); + nVal = 0.0; + } + if ( nFuncFmtType == nCurFmtType ) + nFuncFmtIndex = nCurFmtIndex; + return nVal; +} + +double ScInterpreter::GetDoubleWithDefault(double nDefault) +{ + bool bMissing = IsMissing(); + double nResultVal = GetDouble(); + if ( bMissing ) + nResultVal = nDefault; + return nResultVal; +} + +sal_Int32 ScInterpreter::double_to_int32(double fVal) +{ + if (!std::isfinite(fVal)) + { + SetError( GetDoubleErrorValue( fVal)); + return SAL_MAX_INT32; + } + if (fVal > 0.0) + { + fVal = rtl::math::approxFloor( fVal); + if (fVal > SAL_MAX_INT32) + { + SetError( FormulaError::IllegalArgument); + return SAL_MAX_INT32; + } + } + else if (fVal < 0.0) + { + fVal = rtl::math::approxCeil( fVal); + if (fVal < SAL_MIN_INT32) + { + SetError( FormulaError::IllegalArgument); + return SAL_MAX_INT32; + } + } + return static_cast<sal_Int32>(fVal); +} + +sal_Int32 ScInterpreter::GetInt32() +{ + return double_to_int32(GetDouble()); +} + +sal_Int32 ScInterpreter::GetInt32WithDefault( sal_Int32 nDefault ) +{ + bool bMissing = IsMissing(); + double fVal = GetDouble(); + if ( bMissing ) + return nDefault; + return double_to_int32(fVal); +} + +sal_Int32 ScInterpreter::GetFloor32() +{ + double fVal = GetDouble(); + if (!std::isfinite(fVal)) + { + SetError( GetDoubleErrorValue( fVal)); + return SAL_MAX_INT32; + } + fVal = rtl::math::approxFloor( fVal); + if (fVal < SAL_MIN_INT32 || SAL_MAX_INT32 < fVal) + { + SetError( FormulaError::IllegalArgument); + return SAL_MAX_INT32; + } + return static_cast<sal_Int32>(fVal); +} + +sal_Int16 ScInterpreter::GetInt16() +{ + double fVal = GetDouble(); + if (!std::isfinite(fVal)) + { + SetError( GetDoubleErrorValue( fVal)); + return SAL_MAX_INT16; + } + if (fVal > 0.0) + { + fVal = rtl::math::approxFloor( fVal); + if (fVal > SAL_MAX_INT16) + { + SetError( FormulaError::IllegalArgument); + return SAL_MAX_INT16; + } + } + else if (fVal < 0.0) + { + fVal = rtl::math::approxCeil( fVal); + if (fVal < SAL_MIN_INT16) + { + SetError( FormulaError::IllegalArgument); + return SAL_MAX_INT16; + } + } + return static_cast<sal_Int16>(fVal); +} + +sal_uInt32 ScInterpreter::GetUInt32() +{ + double fVal = rtl::math::approxFloor( GetDouble()); + if (!std::isfinite(fVal)) + { + SetError( GetDoubleErrorValue( fVal)); + return SAL_MAX_UINT32; + } + if (fVal < 0.0 || fVal > SAL_MAX_UINT32) + { + SetError( FormulaError::IllegalArgument); + return SAL_MAX_UINT32; + } + return static_cast<sal_uInt32>(fVal); +} + +bool ScInterpreter::GetDoubleOrString( double& rDouble, svl::SharedString& rString ) +{ + bool bDouble = true; + switch( GetRawStackType() ) + { + case svDouble: + rDouble = PopDouble(); + break; + case svString: + rString = PopString(); + bDouble = false; + break; + case svDoubleRef : + case svSingleRef : + { + ScAddress aAdr; + if (!PopDoubleRefOrSingleRef( aAdr)) + { + rDouble = 0.0; + return true; // caller needs to check nGlobalError + } + ScRefCellValue aCell( mrDoc, aAdr); + if (aCell.hasNumeric()) + { + rDouble = GetCellValue( aAdr, aCell); + } + else + { + GetCellString( rString, aCell); + bDouble = false; + } + } + break; + case svExternalSingleRef: + case svExternalDoubleRef: + case svMatrix: + { + ScMatValType nType = GetDoubleOrStringFromMatrix( rDouble, rString); + bDouble = ScMatrix::IsValueType( nType); + } + break; + case svError: + PopError(); + rDouble = 0.0; + break; + case svEmptyCell: + case svMissing: + Pop(); + rDouble = 0.0; + break; + default: + PopError(); + SetError( FormulaError::IllegalParameter); + rDouble = 0.0; + } + if ( nFuncFmtType == nCurFmtType ) + nFuncFmtIndex = nCurFmtIndex; + return bDouble; +} + +svl::SharedString ScInterpreter::GetString() +{ + switch (GetRawStackType()) + { + case svError: + PopError(); + return svl::SharedString::getEmptyString(); + case svMissing: + case svEmptyCell: + Pop(); + return svl::SharedString::getEmptyString(); + case svDouble: + { + return GetStringFromDouble( PopDouble() ); + } + case svString: + return PopString(); + case svSingleRef: + { + ScAddress aAdr; + PopSingleRef( aAdr ); + if (nGlobalError == FormulaError::NONE) + { + ScRefCellValue aCell(mrDoc, aAdr); + svl::SharedString aSS; + GetCellString(aSS, aCell); + return aSS; + } + else + return svl::SharedString::getEmptyString(); + } + case svDoubleRef: + { // generate position dependent SingleRef + ScRange aRange; + PopDoubleRef( aRange ); + ScAddress aAdr; + if ( nGlobalError == FormulaError::NONE && DoubleRefToPosSingleRef( aRange, aAdr ) ) + { + ScRefCellValue aCell(mrDoc, aAdr); + svl::SharedString aSS; + GetCellString(aSS, aCell); + return aSS; + } + else + return svl::SharedString::getEmptyString(); + } + case svExternalSingleRef: + { + ScExternalRefCache::TokenRef pToken; + PopExternalSingleRef(pToken); + if (nGlobalError != FormulaError::NONE) + return svl::SharedString::getEmptyString(); + + if (pToken->GetType() == svDouble) + { + return GetStringFromDouble( pToken->GetDouble() ); + } + else // svString or svEmpty + return pToken->GetString(); + } + case svExternalDoubleRef: + { + ScMatrixRef pMat; + PopExternalDoubleRef(pMat); + return GetStringFromMatrix(pMat); + } + case svMatrix: + { + ScMatrixRef pMat = PopMatrix(); + return GetStringFromMatrix(pMat); + } + break; + default: + PopError(); + SetError( FormulaError::IllegalArgument); + } + return svl::SharedString::getEmptyString(); +} + +svl::SharedString ScInterpreter::GetStringFromMatrix(const ScMatrixRef& pMat) +{ + if ( !pMat ) + ; // nothing + else if ( !pJumpMatrix ) + { + return pMat->GetString( *pFormatter, 0, 0); + } + else + { + SCSIZE nCols, nRows, nC, nR; + pMat->GetDimensions( nCols, nRows); + pJumpMatrix->GetPos( nC, nR); + // Use vector replication for single row/column arrays. + if ( (nC < nCols || nCols == 1) && (nR < nRows || nRows == 1) ) + return pMat->GetString( *pFormatter, nC, nR); + + SetError( FormulaError::NoValue); + } + return svl::SharedString::getEmptyString(); +} + +ScMatValType ScInterpreter::GetDoubleOrStringFromMatrix( + double& rDouble, svl::SharedString& rString ) +{ + + rDouble = 0.0; + rString = svl::SharedString::getEmptyString(); + ScMatValType nMatValType = ScMatValType::Empty; + + ScMatrixRef pMat; + StackVar eType = GetStackType(); + if (eType == svExternalDoubleRef || eType == svExternalSingleRef || eType == svMatrix) + { + pMat = GetMatrix(); + } + else + { + PopError(); + SetError( FormulaError::IllegalParameter); + return nMatValType; + } + + ScMatrixValue nMatVal; + if (!pMat) + { + // nothing + } + else if (!pJumpMatrix) + { + nMatVal = pMat->Get(0, 0); + nMatValType = nMatVal.nType; + } + else + { + SCSIZE nCols, nRows, nC, nR; + pMat->GetDimensions( nCols, nRows); + pJumpMatrix->GetPos( nC, nR); + // Use vector replication for single row/column arrays. + if ( (nC < nCols || nCols == 1) && (nR < nRows || nRows == 1) ) + { + nMatVal = pMat->Get( nC, nR); + nMatValType = nMatVal.nType; + } + else + SetError( FormulaError::NoValue); + } + + if (ScMatrix::IsValueType( nMatValType)) + { + rDouble = nMatVal.fVal; + FormulaError nError = nMatVal.GetError(); + if (nError != FormulaError::NONE) + SetError( nError); + } + else + { + rString = nMatVal.GetString(); + } + + return nMatValType; +} + +svl::SharedString ScInterpreter::GetStringFromDouble( double fVal ) +{ + sal_uLong nIndex = pFormatter->GetStandardFormat( + SvNumFormatType::NUMBER, + ScGlobal::eLnge); + OUString aStr; + pFormatter->GetInputLineString(fVal, nIndex, aStr); + return mrStrPool.intern(aStr); +} + +void ScInterpreter::ScDBGet() +{ + bool bMissingField = false; + unique_ptr<ScDBQueryParamBase> pQueryParam( GetDBParams(bMissingField) ); + if (!pQueryParam) + { + // Failed to create query param. + PushIllegalParameter(); + return; + } + + pQueryParam->mbSkipString = false; + ScDBQueryDataIterator aValIter(mrDoc, mrContext, std::move(pQueryParam)); + ScDBQueryDataIterator::Value aValue; + if (!aValIter.GetFirst(aValue) || aValue.mnError != FormulaError::NONE) + { + // No match found. + PushNoValue(); + return; + } + + ScDBQueryDataIterator::Value aValNext; + if (aValIter.GetNext(aValNext) && aValNext.mnError == FormulaError::NONE) + { + // There should be only one unique match. + PushIllegalArgument(); + return; + } + + if (aValue.mbIsNumber) + PushDouble(aValue.mfValue); + else + PushString(aValue.maString); +} + +void ScInterpreter::ScExternal() +{ + sal_uInt8 nParamCount = GetByte(); + OUString aUnoName; + OUString aFuncName( pCur->GetExternal().toAsciiUpperCase()); // programmatic name + LegacyFuncData* pLegacyFuncData = ScGlobal::GetLegacyFuncCollection()->findByName(aFuncName); + if (pLegacyFuncData) + { + // Old binary non-UNO add-in function. + // NOTE: parameter count is 1-based with the 0th "parameter" being the + // return value, included in pLegacyFuncDatat->GetParamCount() + if (nParamCount < MAXFUNCPARAM && nParamCount == pLegacyFuncData->GetParamCount() - 1) + { + ParamType eParamType[MAXFUNCPARAM]; + void* ppParam[MAXFUNCPARAM]; + double nVal[MAXFUNCPARAM]; + char* pStr[MAXFUNCPARAM]; + sal_uInt8* pCellArr[MAXFUNCPARAM]; + short i; + + for (i = 0; i < MAXFUNCPARAM; i++) + { + eParamType[i] = pLegacyFuncData->GetParamType(i); + ppParam[i] = nullptr; + nVal[i] = 0.0; + pStr[i] = nullptr; + pCellArr[i] = nullptr; + } + + for (i = nParamCount; (i > 0) && (nGlobalError == FormulaError::NONE); i--) + { + if (IsMissing()) + { + // Old binary Add-In can't distinguish between missing + // omitted argument and 0 (or any other value). Force + // error. + SetError( FormulaError::ParameterExpected); + break; // for + } + switch (eParamType[i]) + { + case ParamType::PTR_DOUBLE : + { + nVal[i-1] = GetDouble(); + ppParam[i] = &nVal[i-1]; + } + break; + case ParamType::PTR_STRING : + { + OString aStr(OUStringToOString(GetString().getString(), + osl_getThreadTextEncoding())); + if ( aStr.getLength() >= ADDIN_MAXSTRLEN ) + SetError( FormulaError::StringOverflow ); + else + { + pStr[i-1] = new char[ADDIN_MAXSTRLEN]; + strncpy( pStr[i-1], aStr.getStr(), ADDIN_MAXSTRLEN ); + pStr[i-1][ADDIN_MAXSTRLEN-1] = 0; + ppParam[i] = pStr[i-1]; + } + } + break; + case ParamType::PTR_DOUBLE_ARR : + { + SCCOL nCol1; + SCROW nRow1; + SCTAB nTab1; + SCCOL nCol2; + SCROW nRow2; + SCTAB nTab2; + PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + pCellArr[i-1] = new sal_uInt8[MAXARRSIZE]; + if (!CreateDoubleArr(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, pCellArr[i-1])) + SetError(FormulaError::CodeOverflow); + else + ppParam[i] = pCellArr[i-1]; + } + break; + case ParamType::PTR_STRING_ARR : + { + SCCOL nCol1; + SCROW nRow1; + SCTAB nTab1; + SCCOL nCol2; + SCROW nRow2; + SCTAB nTab2; + PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + pCellArr[i-1] = new sal_uInt8[MAXARRSIZE]; + if (!CreateStringArr(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, pCellArr[i-1])) + SetError(FormulaError::CodeOverflow); + else + ppParam[i] = pCellArr[i-1]; + } + break; + case ParamType::PTR_CELL_ARR : + { + SCCOL nCol1; + SCROW nRow1; + SCTAB nTab1; + SCCOL nCol2; + SCROW nRow2; + SCTAB nTab2; + PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + pCellArr[i-1] = new sal_uInt8[MAXARRSIZE]; + if (!CreateCellArr(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, pCellArr[i-1])) + SetError(FormulaError::CodeOverflow); + else + ppParam[i] = pCellArr[i-1]; + } + break; + default : + SetError(FormulaError::IllegalParameter); + break; + } + } + while ( i-- ) + Pop(); // In case of error (otherwise i==0) pop all parameters + + if (nGlobalError == FormulaError::NONE) + { + if ( pLegacyFuncData->GetAsyncType() == ParamType::NONE ) + { + switch ( eParamType[0] ) + { + case ParamType::PTR_DOUBLE : + { + double nErg = 0.0; + ppParam[0] = &nErg; + pLegacyFuncData->Call(ppParam); + PushDouble(nErg); + } + break; + case ParamType::PTR_STRING : + { + std::unique_ptr<char[]> pcErg(new char[ADDIN_MAXSTRLEN]); + ppParam[0] = pcErg.get(); + pLegacyFuncData->Call(ppParam); + OUString aUni( pcErg.get(), strlen(pcErg.get()), osl_getThreadTextEncoding() ); + PushString( aUni ); + } + break; + default: + PushError( FormulaError::UnknownState ); + } + } + else + { + // enable asyncs after loading + pArr->AddRecalcMode( ScRecalcMode::ONLOAD_LENIENT ); + // assure identical handler with identical call? + double nErg = 0.0; + ppParam[0] = &nErg; + pLegacyFuncData->Call(ppParam); + sal_uLong nHandle = sal_uLong( nErg ); + if ( nHandle >= 65536 ) + { + ScAddInAsync* pAs = ScAddInAsync::Get( nHandle ); + if ( !pAs ) + { + pAs = new ScAddInAsync(nHandle, pLegacyFuncData, &mrDoc); + pMyFormulaCell->StartListening( *pAs ); + } + else + { + pMyFormulaCell->StartListening( *pAs ); + if ( !pAs->HasDocument( &mrDoc ) ) + pAs->AddDocument( &mrDoc ); + } + if ( pAs->IsValid() ) + { + switch ( pAs->GetType() ) + { + case ParamType::PTR_DOUBLE : + PushDouble( pAs->GetValue() ); + break; + case ParamType::PTR_STRING : + PushString( pAs->GetString() ); + break; + default: + PushError( FormulaError::UnknownState ); + } + } + else + PushNA(); + } + else + PushNoValue(); + } + } + + for (i = 0; i < MAXFUNCPARAM; i++) + { + delete[] pStr[i]; + delete[] pCellArr[i]; + } + } + else + { + while( nParamCount-- > 0) + PopError(); + PushIllegalParameter(); + } + } + else if ( !( aUnoName = ScGlobal::GetAddInCollection()->FindFunction(aFuncName, false) ).isEmpty() ) + { + // bLocalFirst=false in FindFunction, cFunc should be the stored + // internal name + + ScUnoAddInCall aCall( mrDoc, *ScGlobal::GetAddInCollection(), aUnoName, nParamCount ); + + if ( !aCall.ValidParamCount() ) + SetError( FormulaError::IllegalParameter ); + + if ( aCall.NeedsCaller() && GetError() == FormulaError::NONE ) + { + ScDocShell* pShell = mrDoc.GetDocumentShell(); + if (pShell) + aCall.SetCallerFromObjectShell( pShell ); + else + { + // use temporary model object (without document) to supply options + aCall.SetCaller( static_cast<beans::XPropertySet*>( + new ScDocOptionsObj( mrDoc.GetDocOptions() ) ) ); + } + } + + short nPar = nParamCount; + while ( nPar > 0 && GetError() == FormulaError::NONE ) + { + --nPar; // 0 .. (nParamCount-1) + + uno::Any aParam; + if (IsMissing()) + { + // Add-In has to explicitly handle an omitted empty missing + // argument, do not default to anything like GetDouble() would + // do (e.g. 0). + Pop(); + aCall.SetParam( nPar, aParam ); + continue; // while + } + + StackVar nStackType = GetStackType(); + ScAddInArgumentType eType = aCall.GetArgType( nPar ); + switch (eType) + { + case SC_ADDINARG_INTEGER: + { + sal_Int32 nVal = GetInt32(); + if (nGlobalError == FormulaError::NONE) + aParam <<= nVal; + } + break; + + case SC_ADDINARG_DOUBLE: + aParam <<= GetDouble(); + break; + + case SC_ADDINARG_STRING: + aParam <<= GetString().getString(); + break; + + case SC_ADDINARG_INTEGER_ARRAY: + switch( nStackType ) + { + case svDouble: + case svString: + case svSingleRef: + { + sal_Int32 nVal = GetInt32(); + if (nGlobalError == FormulaError::NONE) + { + uno::Sequence<sal_Int32> aInner( &nVal, 1 ); + uno::Sequence< uno::Sequence<sal_Int32> > aOuter( &aInner, 1 ); + aParam <<= aOuter; + } + } + break; + case svDoubleRef: + { + ScRange aRange; + PopDoubleRef( aRange ); + if (!ScRangeToSequence::FillLongArray( aParam, mrDoc, aRange )) + SetError(FormulaError::IllegalParameter); + } + break; + case svMatrix: + if (!ScRangeToSequence::FillLongArray( aParam, PopMatrix().get() )) + SetError(FormulaError::IllegalParameter); + break; + default: + PopError(); + SetError(FormulaError::IllegalParameter); + } + break; + + case SC_ADDINARG_DOUBLE_ARRAY: + switch( nStackType ) + { + case svDouble: + case svString: + case svSingleRef: + { + double fVal = GetDouble(); + uno::Sequence<double> aInner( &fVal, 1 ); + uno::Sequence< uno::Sequence<double> > aOuter( &aInner, 1 ); + aParam <<= aOuter; + } + break; + case svDoubleRef: + { + ScRange aRange; + PopDoubleRef( aRange ); + if (!ScRangeToSequence::FillDoubleArray( aParam, mrDoc, aRange )) + SetError(FormulaError::IllegalParameter); + } + break; + case svMatrix: + if (!ScRangeToSequence::FillDoubleArray( aParam, PopMatrix().get() )) + SetError(FormulaError::IllegalParameter); + break; + default: + PopError(); + SetError(FormulaError::IllegalParameter); + } + break; + + case SC_ADDINARG_STRING_ARRAY: + switch( nStackType ) + { + case svDouble: + case svString: + case svSingleRef: + { + OUString aString = GetString().getString(); + uno::Sequence<OUString> aInner( &aString, 1 ); + uno::Sequence< uno::Sequence<OUString> > aOuter( &aInner, 1 ); + aParam <<= aOuter; + } + break; + case svDoubleRef: + { + ScRange aRange; + PopDoubleRef( aRange ); + if (!ScRangeToSequence::FillStringArray( aParam, mrDoc, aRange )) + SetError(FormulaError::IllegalParameter); + } + break; + case svMatrix: + if (!ScRangeToSequence::FillStringArray( aParam, PopMatrix().get(), pFormatter )) + SetError(FormulaError::IllegalParameter); + break; + default: + PopError(); + SetError(FormulaError::IllegalParameter); + } + break; + + case SC_ADDINARG_MIXED_ARRAY: + switch( nStackType ) + { + case svDouble: + case svString: + case svSingleRef: + { + uno::Any aElem; + if ( nStackType == svDouble ) + aElem <<= GetDouble(); + else if ( nStackType == svString ) + aElem <<= GetString().getString(); + else + { + ScAddress aAdr; + if ( PopDoubleRefOrSingleRef( aAdr ) ) + { + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasString()) + { + svl::SharedString aStr; + GetCellString(aStr, aCell); + aElem <<= aStr.getString(); + } + else + aElem <<= GetCellValue(aAdr, aCell); + } + } + uno::Sequence<uno::Any> aInner( &aElem, 1 ); + uno::Sequence< uno::Sequence<uno::Any> > aOuter( &aInner, 1 ); + aParam <<= aOuter; + } + break; + case svDoubleRef: + { + ScRange aRange; + PopDoubleRef( aRange ); + if (!ScRangeToSequence::FillMixedArray( aParam, mrDoc, aRange )) + SetError(FormulaError::IllegalParameter); + } + break; + case svMatrix: + if (!ScRangeToSequence::FillMixedArray( aParam, PopMatrix().get() )) + SetError(FormulaError::IllegalParameter); + break; + default: + PopError(); + SetError(FormulaError::IllegalParameter); + } + break; + + case SC_ADDINARG_VALUE_OR_ARRAY: + switch( nStackType ) + { + case svDouble: + aParam <<= GetDouble(); + break; + case svString: + aParam <<= GetString().getString(); + break; + case svSingleRef: + { + ScAddress aAdr; + if ( PopDoubleRefOrSingleRef( aAdr ) ) + { + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasString()) + { + svl::SharedString aStr; + GetCellString(aStr, aCell); + aParam <<= aStr.getString(); + } + else + aParam <<= GetCellValue(aAdr, aCell); + } + } + break; + case svDoubleRef: + { + ScRange aRange; + PopDoubleRef( aRange ); + if (!ScRangeToSequence::FillMixedArray( aParam, mrDoc, aRange )) + SetError(FormulaError::IllegalParameter); + } + break; + case svMatrix: + if (!ScRangeToSequence::FillMixedArray( aParam, PopMatrix().get() )) + SetError(FormulaError::IllegalParameter); + break; + default: + PopError(); + SetError(FormulaError::IllegalParameter); + } + break; + + case SC_ADDINARG_CELLRANGE: + switch( nStackType ) + { + case svSingleRef: + { + ScAddress aAdr; + PopSingleRef( aAdr ); + ScRange aRange( aAdr ); + uno::Reference<table::XCellRange> xObj = + ScCellRangeObj::CreateRangeFromDoc( mrDoc, aRange ); + if (xObj.is()) + aParam <<= xObj; + else + SetError(FormulaError::IllegalParameter); + } + break; + case svDoubleRef: + { + ScRange aRange; + PopDoubleRef( aRange ); + uno::Reference<table::XCellRange> xObj = + ScCellRangeObj::CreateRangeFromDoc( mrDoc, aRange ); + if (xObj.is()) + { + aParam <<= xObj; + } + else + { + SetError(FormulaError::IllegalParameter); + } + } + break; + default: + PopError(); + SetError(FormulaError::IllegalParameter); + } + break; + + default: + PopError(); + SetError(FormulaError::IllegalParameter); + } + aCall.SetParam( nPar, aParam ); + } + + while (nPar-- > 0) + { + Pop(); // in case of error, remove remaining args + } + if ( GetError() == FormulaError::NONE ) + { + aCall.ExecuteCall(); + + if ( aCall.HasVarRes() ) // handle async functions + { + pArr->AddRecalcMode( ScRecalcMode::ONLOAD_LENIENT ); + uno::Reference<sheet::XVolatileResult> xRes = aCall.GetVarRes(); + ScAddInListener* pLis = ScAddInListener::Get( xRes ); + // In case there is no pMyFormulaCell, i.e. while interpreting + // temporarily from within the Function Wizard, try to obtain a + // valid result from an existing listener for that volatile, or + // create a new and hope for an immediate result. If none + // available that should lead to a void result and thus #N/A. + bool bTemporaryListener = false; + if ( !pLis ) + { + pLis = ScAddInListener::CreateListener( xRes, &mrDoc ); + if (pMyFormulaCell) + pMyFormulaCell->StartListening( *pLis ); + else + bTemporaryListener = true; + } + else if (pMyFormulaCell) + { + pMyFormulaCell->StartListening( *pLis ); + if ( !pLis->HasDocument( &mrDoc ) ) + { + pLis->AddDocument( &mrDoc ); + } + } + + aCall.SetResult( pLis->GetResult() ); // use result from async + + if (bTemporaryListener) + { + try + { + // EventObject can be any, not evaluated by + // ScAddInListener::disposing() + css::lang::EventObject aEvent; + pLis->disposing(aEvent); // pLis is dead hereafter + } + catch (const uno::Exception&) + { + } + } + } + + if ( aCall.GetErrCode() != FormulaError::NONE ) + { + PushError( aCall.GetErrCode() ); + } + else if ( aCall.HasMatrix() ) + { + PushMatrix( aCall.GetMatrix() ); + } + else if ( aCall.HasString() ) + { + PushString( aCall.GetString() ); + } + else + { + PushDouble( aCall.GetValue() ); + } + } + else // error... + PushError( GetError()); + } + else + { + while( nParamCount-- > 0) + { + PopError(); + } + PushError( FormulaError::NoAddin ); + } +} + +void ScInterpreter::ScMissing() +{ + if ( aCode.IsEndOfPath() ) + PushTempToken( new ScEmptyCellToken( false, false ) ); + else + PushTempToken( new FormulaMissingToken ); +} + +#if HAVE_FEATURE_SCRIPTING + +static uno::Any lcl_getSheetModule( const uno::Reference<table::XCellRange>& xCellRange, const ScDocument* pDok ) +{ + uno::Reference< sheet::XSheetCellRange > xSheetRange( xCellRange, uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySet > xProps( xSheetRange->getSpreadsheet(), uno::UNO_QUERY_THROW ); + OUString sCodeName; + xProps->getPropertyValue("CodeName") >>= sCodeName; + // #TODO #FIXME ideally we should 'throw' here if we don't get a valid parent, but... it is possible + // to create a module ( and use 'Option VBASupport 1' ) for a calc document, in this scenario there + // are *NO* special document module objects ( of course being able to switch between vba/non vba mode at + // the document in the future could fix this, especially IF the switching of the vba mode takes care to + // create the special document module objects if they don't exist. + BasicManager* pBasMgr = pDok->GetDocumentShell()->GetBasicManager(); + + uno::Reference< uno::XInterface > xIf; + if ( pBasMgr && !pBasMgr->GetName().isEmpty() ) + { + OUString sProj( "Standard" ); + if ( !pDok->GetDocumentShell()->GetBasicManager()->GetName().isEmpty() ) + { + sProj = pDok->GetDocumentShell()->GetBasicManager()->GetName(); + } + StarBASIC* pBasic = pDok->GetDocumentShell()->GetBasicManager()->GetLib( sProj ); + if ( pBasic ) + { + SbModule* pMod = pBasic->FindModule( sCodeName ); + if ( pMod ) + { + xIf = pMod->GetUnoModule(); + } + } + } + return uno::Any( xIf ); +} + +static bool lcl_setVBARange( const ScRange& aRange, const ScDocument& rDok, SbxVariable* pPar ) +{ + bool bOk = false; + try + { + uno::Reference< uno::XInterface > xVBARange; + uno::Reference<table::XCellRange> xCellRange = ScCellRangeObj::CreateRangeFromDoc( rDok, aRange ); + uno::Sequence< uno::Any > aArgs{ lcl_getSheetModule( xCellRange, &rDok ), + uno::Any(xCellRange) }; + xVBARange = ooo::vba::createVBAUnoAPIServiceWithArgs( rDok.GetDocumentShell(), "ooo.vba.excel.Range", aArgs ); + if ( xVBARange.is() ) + { + SbxObjectRef aObj = GetSbUnoObject( "A-Range", uno::Any( xVBARange ) ); + SetSbUnoObjectDfltPropName( aObj.get() ); + bOk = pPar->PutObject( aObj.get() ); + } + } + catch( uno::Exception& ) + { + } + return bOk; +} + +static bool lcl_isNumericResult( double& fVal, const SbxVariable* pVar ) +{ + switch (pVar->GetType()) + { + case SbxINTEGER: + case SbxLONG: + case SbxSINGLE: + case SbxDOUBLE: + case SbxCURRENCY: + case SbxDATE: + case SbxUSHORT: + case SbxULONG: + case SbxINT: + case SbxUINT: + case SbxSALINT64: + case SbxSALUINT64: + case SbxDECIMAL: + fVal = pVar->GetDouble(); + return true; + case SbxBOOL: + fVal = (pVar->GetBool() ? 1.0 : 0.0); + return true; + default: + ; // nothing + } + return false; +} + +#endif + +void ScInterpreter::ScMacro() +{ + +#if !HAVE_FEATURE_SCRIPTING + PushNoValue(); // without DocShell no CallBasic + return; +#else + SbxBase::ResetError(); + + sal_uInt8 nParamCount = GetByte(); + OUString aMacro( pCur->GetExternal() ); + + ScDocShell* pDocSh = mrDoc.GetDocumentShell(); + if ( !pDocSh ) + { + PushNoValue(); // without DocShell no CallBasic + return; + } + + // no security queue beforehand (just CheckMacroWarn), moved to CallBasic + + // If the Dok was loaded during a Basic-Calls, + // is the Sbx-object created(?) +// pDocSh->GetSbxObject(); + + // search function with the name, + // then assemble SfxObjectShell::CallBasic from aBasicStr, aMacroStr + + StarBASIC* pRoot; + + try + { + pRoot = pDocSh->GetBasic(); + } + catch (...) + { + pRoot = nullptr; + } + + SbxVariable* pVar = pRoot ? pRoot->Find(aMacro, SbxClassType::Method) : nullptr; + if( !pVar || pVar->GetType() == SbxVOID ) + { + PushError( FormulaError::NoMacro ); + return; + } + SbMethod* pMethod = dynamic_cast<SbMethod*>(pVar); + if( !pMethod ) + { + PushError( FormulaError::NoMacro ); + return; + } + + bool bVolatileMacro = false; + + SbModule* pModule = pMethod->GetModule(); + bool bUseVBAObjects = pModule->IsVBASupport(); + SbxObject* pObject = pModule->GetParent(); + OSL_ENSURE(dynamic_cast<const StarBASIC *>(pObject) != nullptr, "No Basic found!"); + OUString aMacroStr = pObject->GetName() + "." + pModule->GetName() + "." + pMethod->GetName(); + OUString aBasicStr; + if (pRoot && bUseVBAObjects) + { + // just here to make sure the VBA objects when we run the macro during ODF import + pRoot->getVBAGlobals(); + } + if (pObject->GetParent()) + { + aBasicStr = pObject->GetParent()->GetName(); // document BASIC + } + else + { + aBasicStr = SfxGetpApp()->GetName(); // application BASIC + } + // assemble a parameter array + + SbxArrayRef refPar = new SbxArray; + bool bOk = true; + for( sal_uInt32 i = nParamCount; i && bOk ; i-- ) + { + SbxVariable* pPar = refPar->Get(i); + switch( GetStackType() ) + { + case svDouble: + pPar->PutDouble( GetDouble() ); + break; + case svString: + pPar->PutString( GetString().getString() ); + break; + case svExternalSingleRef: + { + ScExternalRefCache::TokenRef pToken; + PopExternalSingleRef(pToken); + if (nGlobalError != FormulaError::NONE) + bOk = false; + else + { + if ( pToken->GetType() == svString ) + pPar->PutString( pToken->GetString().getString() ); + else if ( pToken->GetType() == svDouble ) + pPar->PutDouble( pToken->GetDouble() ); + else + { + SetError( FormulaError::IllegalArgument ); + bOk = false; + } + } + } + break; + case svSingleRef: + { + ScAddress aAdr; + PopSingleRef( aAdr ); + if ( bUseVBAObjects ) + { + ScRange aRange( aAdr ); + bOk = lcl_setVBARange( aRange, mrDoc, pPar ); + } + else + { + bOk = SetSbxVariable( pPar, aAdr ); + } + } + break; + case svDoubleRef: + { + SCCOL nCol1; + SCROW nRow1; + SCTAB nTab1; + SCCOL nCol2; + SCROW nRow2; + SCTAB nTab2; + PopDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + if( nTab1 != nTab2 ) + { + SetError( FormulaError::IllegalParameter ); + bOk = false; + } + else + { + if ( bUseVBAObjects ) + { + ScRange aRange( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + bOk = lcl_setVBARange( aRange, mrDoc, pPar ); + } + else + { + SbxDimArrayRef refArray = new SbxDimArray; + refArray->AddDim(1, nRow2 - nRow1 + 1); + refArray->AddDim(1, nCol2 - nCol1 + 1); + ScAddress aAdr( nCol1, nRow1, nTab1 ); + for( SCROW nRow = nRow1; bOk && nRow <= nRow2; nRow++ ) + { + aAdr.SetRow( nRow ); + sal_Int32 nIdx[ 2 ]; + nIdx[ 0 ] = nRow-nRow1+1; + for( SCCOL nCol = nCol1; bOk && nCol <= nCol2; nCol++ ) + { + aAdr.SetCol( nCol ); + nIdx[ 1 ] = nCol-nCol1+1; + SbxVariable* p = refArray->Get(nIdx); + bOk = SetSbxVariable( p, aAdr ); + } + } + pPar->PutObject( refArray.get() ); + } + } + } + break; + case svExternalDoubleRef: + case svMatrix: + { + ScMatrixRef pMat = GetMatrix(); + SCSIZE nC, nR; + if (pMat && nGlobalError == FormulaError::NONE) + { + pMat->GetDimensions(nC, nR); + SbxDimArrayRef refArray = new SbxDimArray; + refArray->AddDim(1, static_cast<sal_Int32>(nR)); + refArray->AddDim(1, static_cast<sal_Int32>(nC)); + for( SCSIZE nMatRow = 0; nMatRow < nR; nMatRow++ ) + { + sal_Int32 nIdx[ 2 ]; + nIdx[ 0 ] = static_cast<sal_Int32>(nMatRow+1); + for( SCSIZE nMatCol = 0; nMatCol < nC; nMatCol++ ) + { + nIdx[ 1 ] = static_cast<sal_Int32>(nMatCol+1); + SbxVariable* p = refArray->Get(nIdx); + if (pMat->IsStringOrEmpty(nMatCol, nMatRow)) + { + p->PutString( pMat->GetString(nMatCol, nMatRow).getString() ); + } + else + { + p->PutDouble( pMat->GetDouble(nMatCol, nMatRow)); + } + } + } + pPar->PutObject( refArray.get() ); + } + else + { + SetError( FormulaError::IllegalParameter ); + } + } + break; + default: + SetError( FormulaError::IllegalParameter ); + bOk = false; + } + } + if( bOk ) + { + mrDoc.LockTable( aPos.Tab() ); + SbxVariableRef refRes = new SbxVariable; + mrDoc.IncMacroInterpretLevel(); + ErrCode eRet = pDocSh->CallBasic( aMacroStr, aBasicStr, refPar.get(), refRes.get() ); + mrDoc.DecMacroInterpretLevel(); + mrDoc.UnlockTable( aPos.Tab() ); + + ScMacroManager* pMacroMgr = mrDoc.GetMacroManager(); + if (pMacroMgr) + { + bVolatileMacro = pMacroMgr->GetUserFuncVolatile( pMethod->GetName() ); + pMacroMgr->AddDependentCell(pModule->GetName(), pMyFormulaCell); + } + + double fVal; + SbxDataType eResType = refRes->GetType(); + if( SbxBase::GetError() ) + { + SetError( FormulaError::NoValue); + } + if ( eRet != ERRCODE_NONE ) + { + PushNoValue(); + } + else if (lcl_isNumericResult( fVal, refRes.get())) + { + switch (eResType) + { + case SbxDATE: + nFuncFmtType = SvNumFormatType::DATE; + break; + case SbxBOOL: + nFuncFmtType = SvNumFormatType::LOGICAL; + break; + // Do not add SbxCURRENCY, we don't know which currency. + default: + ; // nothing + } + PushDouble( fVal ); + } + else if ( eResType & SbxARRAY ) + { + SbxBase* pElemObj = refRes->GetObject(); + SbxDimArray* pDimArray = dynamic_cast<SbxDimArray*>(pElemObj); + sal_Int32 nDim = pDimArray ? pDimArray->GetDims() : 0; + if ( 1 <= nDim && nDim <= 2 ) + { + sal_Int32 nCs, nCe, nRs; + SCSIZE nC, nR; + SCCOL nColIdx; + SCROW nRowIdx; + if ( nDim == 1 ) + { // array( cols ) one line, several columns + pDimArray->GetDim(1, nCs, nCe); + nC = static_cast<SCSIZE>(nCe - nCs + 1); + nRs = 0; + nR = 1; + nColIdx = 0; + nRowIdx = 1; + } + else + { // array( rows, cols ) + sal_Int32 nRe; + pDimArray->GetDim(1, nRs, nRe); + nR = static_cast<SCSIZE>(nRe - nRs + 1); + pDimArray->GetDim(2, nCs, nCe); + nC = static_cast<SCSIZE>(nCe - nCs + 1); + nColIdx = 1; + nRowIdx = 0; + } + ScMatrixRef pMat = GetNewMat( nC, nR, /*bEmpty*/true); + if ( pMat ) + { + SbxVariable* pV; + for ( SCSIZE j=0; j < nR; j++ ) + { + sal_Int32 nIdx[ 2 ]; + // in one-dimensional array( cols ) nIdx[1] + // from SbxDimArray::Get is ignored + nIdx[ nRowIdx ] = nRs + static_cast<sal_Int32>(j); + for ( SCSIZE i=0; i < nC; i++ ) + { + nIdx[ nColIdx ] = nCs + static_cast<sal_Int32>(i); + pV = pDimArray->Get(nIdx); + if ( lcl_isNumericResult( fVal, pV) ) + { + pMat->PutDouble( fVal, i, j ); + } + else + { + pMat->PutString(mrStrPool.intern(pV->GetOUString()), i, j); + } + } + } + PushMatrix( pMat ); + } + else + { + PushIllegalArgument(); + } + } + else + { + PushNoValue(); + } + } + else + { + PushString( refRes->GetOUString() ); + } + } + + if (bVolatileMacro && meVolatileType == NOT_VOLATILE) + meVolatileType = VOLATILE_MACRO; +#endif +} + +#if HAVE_FEATURE_SCRIPTING + +bool ScInterpreter::SetSbxVariable( SbxVariable* pVar, const ScAddress& rPos ) +{ + bool bOk = true; + ScRefCellValue aCell(mrDoc, rPos); + if (!aCell.isEmpty()) + { + FormulaError nErr; + double nVal; + switch (aCell.getType()) + { + case CELLTYPE_VALUE : + nVal = GetValueCellValue(rPos, aCell.getDouble()); + pVar->PutDouble( nVal ); + break; + case CELLTYPE_STRING : + case CELLTYPE_EDIT : + pVar->PutString(aCell.getString(&mrDoc)); + break; + case CELLTYPE_FORMULA : + nErr = aCell.getFormula()->GetErrCode(); + if( nErr == FormulaError::NONE ) + { + if (aCell.getFormula()->IsValue()) + { + nVal = aCell.getFormula()->GetValue(); + pVar->PutDouble( nVal ); + } + else + pVar->PutString(aCell.getFormula()->GetString().getString()); + } + else + { + SetError( nErr ); + bOk = false; + } + break; + default : + pVar->PutEmpty(); + } + } + else + pVar->PutEmpty(); + + return bOk; +} + +#endif + +void ScInterpreter::ScTableOp() +{ + sal_uInt8 nParamCount = GetByte(); + if (nParamCount != 3 && nParamCount != 5) + { + PushIllegalParameter(); + return; + } + ScInterpreterTableOpParams aTableOp; + if (nParamCount == 5) + { + PopSingleRef( aTableOp.aNew2 ); + PopSingleRef( aTableOp.aOld2 ); + } + PopSingleRef( aTableOp.aNew1 ); + PopSingleRef( aTableOp.aOld1 ); + PopSingleRef( aTableOp.aFormulaPos ); + + aTableOp.bValid = true; + mrDoc.m_TableOpList.push_back(&aTableOp); + mrDoc.IncInterpreterTableOpLevel(); + + bool bReuseLastParams = (mrDoc.aLastTableOpParams == aTableOp); + if ( bReuseLastParams ) + { + aTableOp.aNotifiedFormulaPos = mrDoc.aLastTableOpParams.aNotifiedFormulaPos; + aTableOp.bRefresh = true; + for ( const auto& rPos : aTableOp.aNotifiedFormulaPos ) + { // emulate broadcast and indirectly collect cell pointers + ScRefCellValue aCell(mrDoc, rPos); + if (aCell.getType() == CELLTYPE_FORMULA) + aCell.getFormula()->SetTableOpDirty(); + } + } + else + { // broadcast and indirectly collect cell pointers and positions + mrDoc.SetTableOpDirty( aTableOp.aOld1 ); + if ( nParamCount == 5 ) + mrDoc.SetTableOpDirty( aTableOp.aOld2 ); + } + aTableOp.bCollectNotifications = false; + + ScRefCellValue aCell(mrDoc, aTableOp.aFormulaPos); + if (aCell.getType() == CELLTYPE_FORMULA) + aCell.getFormula()->SetDirtyVar(); + if (aCell.hasNumeric()) + { + PushDouble(GetCellValue(aTableOp.aFormulaPos, aCell)); + } + else + { + svl::SharedString aCellString; + GetCellString(aCellString, aCell); + PushString( aCellString ); + } + + auto const itr = + ::std::find(mrDoc.m_TableOpList.begin(), mrDoc.m_TableOpList.end(), &aTableOp); + if (itr != mrDoc.m_TableOpList.end()) + { + mrDoc.m_TableOpList.erase(itr); + } + + // set dirty again once more to be able to recalculate original + for ( const auto& pCell : aTableOp.aNotifiedFormulaCells ) + { + pCell->SetTableOpDirty(); + } + + // save these params for next incarnation + if ( !bReuseLastParams ) + mrDoc.aLastTableOpParams = aTableOp; + + if (aCell.getType() == CELLTYPE_FORMULA) + { + aCell.getFormula()->SetDirtyVar(); + aCell.getFormula()->GetErrCode(); // recalculate original + } + + // Reset all dirty flags so next incarnation does really collect all cell + // pointers during notifications and not just non-dirty ones, which may + // happen if a formula cell is used by more than one TableOp block. + for ( const auto& pCell : aTableOp.aNotifiedFormulaCells ) + { + pCell->ResetTableOpDirtyVar(); + } + + mrDoc.DecInterpreterTableOpLevel(); +} + +void ScInterpreter::ScDBArea() +{ + ScDBData* pDBData = mrDoc.GetDBCollection()->getNamedDBs().findByIndex(pCur->GetIndex()); + if (pDBData) + { + ScComplexRefData aRefData; + aRefData.InitFlags(); + ScRange aRange; + pDBData->GetArea(aRange); + aRange.aEnd.SetTab(aRange.aStart.Tab()); + aRefData.SetRange(mrDoc.GetSheetLimits(), aRange, aPos); + PushTempToken( new ScDoubleRefToken( mrDoc.GetSheetLimits(), aRefData ) ); + } + else + PushError( FormulaError::NoName); +} + +void ScInterpreter::ScColRowNameAuto() +{ + ScComplexRefData aRefData( *pCur->GetDoubleRef() ); + ScRange aAbs = aRefData.toAbs(mrDoc, aPos); + if (!mrDoc.ValidRange(aAbs)) + { + PushError( FormulaError::NoRef ); + return; + } + + SCCOL nStartCol; + SCROW nStartRow; + + // maybe remember limit by using defined ColRowNameRange + SCCOL nCol2 = aAbs.aEnd.Col(); + SCROW nRow2 = aAbs.aEnd.Row(); + // DataArea of the first cell + nStartCol = aAbs.aStart.Col(); + nStartRow = aAbs.aStart.Row(); + aAbs.aEnd = aAbs.aStart; // Shrink to the top-left cell. + + { + // Expand to the data area. Only modify the end position. + SCCOL nDACol1 = aAbs.aStart.Col(), nDACol2 = aAbs.aEnd.Col(); + SCROW nDARow1 = aAbs.aStart.Row(), nDARow2 = aAbs.aEnd.Row(); + mrDoc.GetDataArea(aAbs.aStart.Tab(), nDACol1, nDARow1, nDACol2, nDARow2, true, false); + aAbs.aEnd.SetCol(nDACol2); + aAbs.aEnd.SetRow(nDARow2); + } + + // corresponds with ScCompiler::GetToken + if ( aRefData.Ref1.IsColRel() ) + { // ColName + aAbs.aEnd.SetCol(nStartCol); + // maybe get previous limit by using defined ColRowNameRange + if (aAbs.aEnd.Row() > nRow2) + aAbs.aEnd.SetRow(nRow2); + if ( aPos.Col() == nStartCol ) + { + SCROW nMyRow = aPos.Row(); + if ( nStartRow <= nMyRow && nMyRow <= aAbs.aEnd.Row()) + { //Formula in the same column and within the range + if ( nMyRow == nStartRow ) + { // take the rest under the name + nStartRow++; + if ( nStartRow > mrDoc.MaxRow() ) + nStartRow = mrDoc.MaxRow(); + aAbs.aStart.SetRow(nStartRow); + } + else + { // below the name to the formula cell + aAbs.aEnd.SetRow(nMyRow - 1); + } + } + } + } + else + { // RowName + aAbs.aEnd.SetRow(nStartRow); + // maybe get previous limit by using defined ColRowNameRange + if (aAbs.aEnd.Col() > nCol2) + aAbs.aEnd.SetCol(nCol2); + if ( aPos.Row() == nStartRow ) + { + SCCOL nMyCol = aPos.Col(); + if (nStartCol <= nMyCol && nMyCol <= aAbs.aEnd.Col()) + { //Formula in the same column and within the range + if ( nMyCol == nStartCol ) + { // take the rest under the name + nStartCol++; + if ( nStartCol > mrDoc.MaxCol() ) + nStartCol = mrDoc.MaxCol(); + aAbs.aStart.SetCol(nStartCol); + } + else + { // below the name to the formula cell + aAbs.aEnd.SetCol(nMyCol - 1); + } + } + } + } + aRefData.SetRange(mrDoc.GetSheetLimits(), aAbs, aPos); + PushTempToken( new ScDoubleRefToken( mrDoc.GetSheetLimits(), aRefData ) ); +} + +// --- internals ------------------------------------------------------------ + +void ScInterpreter::ScTTT() +{ // temporary test, testing functions etc. + sal_uInt8 nParamCount = GetByte(); + // do something, count down nParamCount with Pops! + + // clean up Stack + while ( nParamCount-- > 0) + Pop(); + PushError(FormulaError::NoValue); +} + +ScInterpreter::ScInterpreter( ScFormulaCell* pCell, ScDocument& rDoc, ScInterpreterContext& rContext, + const ScAddress& rPos, ScTokenArray& r, bool bForGroupThreading ) + : aCode(r) + , aPos(rPos) + , pArr(&r) + , mrContext(rContext) + , mrDoc(rDoc) + , mpLinkManager(rDoc.GetLinkManager()) + , mrStrPool(rDoc.GetSharedStringPool()) + , pJumpMatrix(nullptr) + , pMyFormulaCell(pCell) + , pFormatter(rContext.GetFormatTable()) + , pCur(nullptr) + , nGlobalError(FormulaError::NONE) + , sp(0) + , maxsp(0) + , nFuncFmtIndex(0) + , nCurFmtIndex(0) + , nRetFmtIndex(0) + , nFuncFmtType(SvNumFormatType::ALL) + , nCurFmtType(SvNumFormatType::ALL) + , nRetFmtType(SvNumFormatType::ALL) + , mnStringNoValueError(FormulaError::NoValue) + , mnSubTotalFlags(SubtotalFlags::NONE) + , cPar(0) + , bCalcAsShown(rDoc.GetDocOptions().IsCalcAsShown()) + , meVolatileType(r.IsRecalcModeAlways() ? VOLATILE : NOT_VOLATILE) +{ + MergeCalcConfig(); + + if(pMyFormulaCell) + { + ScMatrixMode cMatFlag = pMyFormulaCell->GetMatrixFlag(); + bMatrixFormula = ( cMatFlag == ScMatrixMode::Formula ); + } + else + bMatrixFormula = false; + + // Lets not use the global stack while formula-group-threading. + // as it complicates its life-cycle mgmt since for threading formula-groups, + // ScInterpreter is preallocated (in main thread) for each worker thread. + if (!bGlobalStackInUse && !bForGroupThreading) + { + bGlobalStackInUse = true; + if (!pGlobalStack) + pGlobalStack.reset(new ScTokenStack); + pStackObj = pGlobalStack.get(); + } + else + { + pStackObj = new ScTokenStack; + } + pStack = pStackObj->pPointer; +} + +ScInterpreter::~ScInterpreter() +{ + if ( pStackObj == pGlobalStack.get() ) + bGlobalStackInUse = false; + else + delete pStackObj; +} + +void ScInterpreter::Init( ScFormulaCell* pCell, const ScAddress& rPos, ScTokenArray& rTokArray ) +{ + aCode.ReInit(rTokArray); + aPos = rPos; + pArr = &rTokArray; + xResult = nullptr; + pJumpMatrix = nullptr; + maTokenMatrixMap.clear(); + pMyFormulaCell = pCell; + pCur = nullptr; + nGlobalError = FormulaError::NONE; + sp = 0; + maxsp = 0; + nFuncFmtIndex = 0; + nCurFmtIndex = 0; + nRetFmtIndex = 0; + nFuncFmtType = SvNumFormatType::ALL; + nCurFmtType = SvNumFormatType::ALL; + nRetFmtType = SvNumFormatType::ALL; + mnStringNoValueError = FormulaError::NoValue; + mnSubTotalFlags = SubtotalFlags::NONE; + cPar = 0; +} + +ScCalcConfig& ScInterpreter::GetOrCreateGlobalConfig() +{ + if (!mpGlobalConfig) + mpGlobalConfig = new ScCalcConfig(); + return *mpGlobalConfig; +} + +void ScInterpreter::SetGlobalConfig(const ScCalcConfig& rConfig) +{ + GetOrCreateGlobalConfig() = rConfig; +} + +const ScCalcConfig& ScInterpreter::GetGlobalConfig() +{ + return GetOrCreateGlobalConfig(); +} + +void ScInterpreter::MergeCalcConfig() +{ + maCalcConfig = GetOrCreateGlobalConfig(); + maCalcConfig.MergeDocumentSpecific( mrDoc.GetCalcConfig()); +} + +void ScInterpreter::GlobalExit() +{ + OSL_ENSURE(!bGlobalStackInUse, "who is still using the TokenStack?"); + pGlobalStack.reset(); +} + +namespace { + +double applyImplicitIntersection(const sc::RangeMatrix& rMat, const ScAddress& rPos) +{ + if (rMat.mnRow1 <= rPos.Row() && rPos.Row() <= rMat.mnRow2 && rMat.mnCol1 == rMat.mnCol2) + { + SCROW nOffset = rPos.Row() - rMat.mnRow1; + return rMat.mpMat->GetDouble(0, nOffset); + } + + if (rMat.mnCol1 <= rPos.Col() && rPos.Col() <= rMat.mnCol2 && rMat.mnRow1 == rMat.mnRow2) + { + SCROW nOffset = rPos.Col() - rMat.mnCol1; + return rMat.mpMat->GetDouble(nOffset, 0); + } + + return std::numeric_limits<double>::quiet_NaN(); +} + +// Test for Functions that evaluate an error code and directly set nGlobalError to 0 +bool IsErrFunc(OpCode oc) +{ + switch (oc) + { + case ocCount : + case ocCount2 : + case ocErrorType : + case ocIsEmpty : + case ocIsErr : + case ocIsError : + case ocIsFormula : + case ocIsLogical : + case ocIsNA : + case ocIsNonString : + case ocIsRef : + case ocIsString : + case ocIsValue : + case ocN : + case ocType : + case ocIfError : + case ocIfNA : + case ocErrorType_ODF : + case ocAggregate: // may ignore errors depending on option + case ocIfs_MS: + case ocSwitch_MS: + return true; + default: + return false; + } +} + +} //namespace + +StackVar ScInterpreter::Interpret() +{ + SvNumFormatType nRetTypeExpr = SvNumFormatType::UNDEFINED; + sal_uInt32 nRetIndexExpr = 0; + sal_uInt16 nErrorFunction = 0; + sal_uInt16 nErrorFunctionCount = 0; + std::vector<sal_uInt16> aErrorFunctionStack; + sal_uInt16 nStackBase; + + nGlobalError = FormulaError::NONE; + nStackBase = sp = maxsp = 0; + nRetFmtType = SvNumFormatType::UNDEFINED; + nFuncFmtType = SvNumFormatType::UNDEFINED; + nFuncFmtIndex = nCurFmtIndex = nRetFmtIndex = 0; + xResult = nullptr; + pJumpMatrix = nullptr; + mnSubTotalFlags = SubtotalFlags::NONE; + ScTokenMatrixMap::const_iterator aTokenMatrixMapIter; + + // Once upon a time we used to have FP exceptions on, and there was a + // Windows printer driver that kept switching off exceptions, so we had to + // switch them back on again every time. Who knows if there isn't a driver + // that keeps switching exceptions on, now that we run with exceptions off, + // so reassure exceptions are really off. + SAL_MATH_FPEXCEPTIONS_OFF(); + + OpCode eOp = ocNone; + aCode.Reset(); + for (;;) + { + pCur = aCode.Next(); + if (!pCur || (nGlobalError != FormulaError::NONE && nErrorFunction > nErrorFunctionCount) ) + break; + eOp = pCur->GetOpCode(); + cPar = pCur->GetByte(); + if ( eOp == ocPush ) + { + // RPN code push without error + PushWithoutError( *pCur ); + nCurFmtType = SvNumFormatType::UNDEFINED; + } + else if (!FormulaCompiler::IsOpCodeJumpCommand( eOp ) && + ((aTokenMatrixMapIter = maTokenMatrixMap.find( pCur)) != + maTokenMatrixMap.end()) && + (*aTokenMatrixMapIter).second->GetType() != svJumpMatrix) + { + // Path already calculated, reuse result. + if (sp >= pCur->GetParamCount()) + nStackBase = sp - pCur->GetParamCount(); + else + { + SAL_WARN("sc.core", "Stack anomaly with calculated path at " + << aPos.Tab() << "," << aPos.Col() << "," << aPos.Row() + << " " << aPos.Format( + ScRefFlags::VALID | ScRefFlags::FORCE_DOC | ScRefFlags::TAB_3D, &mrDoc) + << " eOp: " << static_cast<int>(eOp) + << " params: " << static_cast<int>(pCur->GetParamCount()) + << " nStackBase: " << nStackBase << " sp: " << sp); + nStackBase = sp; + assert(!"underflow"); + } + sp = nStackBase; + PushTokenRef( (*aTokenMatrixMapIter).second); + } + else + { + // previous expression determines the current number format + nCurFmtType = nRetTypeExpr; + nCurFmtIndex = nRetIndexExpr; + // default function's format, others are set if needed + nFuncFmtType = SvNumFormatType::NUMBER; + nFuncFmtIndex = 0; + + if (FormulaCompiler::IsOpCodeJumpCommand( eOp )) + nStackBase = sp; // don't mess around with the jumps + else + { + // Convert parameters to matrix if in array/matrix formula and + // parameters of function indicate doing so. Create JumpMatrix + // if necessary. + if ( MatrixParameterConversion() ) + { + eOp = ocNone; // JumpMatrix created + nStackBase = sp; + } + else if (sp >= pCur->GetParamCount()) + nStackBase = sp - pCur->GetParamCount(); + else + { + SAL_WARN("sc.core", "Stack anomaly at " << aPos.Tab() << "," << aPos.Col() << "," << aPos.Row() + << " " << aPos.Format( + ScRefFlags::VALID | ScRefFlags::FORCE_DOC | ScRefFlags::TAB_3D, &mrDoc) + << " eOp: " << static_cast<int>(eOp) + << " params: " << static_cast<int>(pCur->GetParamCount()) + << " nStackBase: " << nStackBase << " sp: " << sp); + nStackBase = sp; + assert(!"underflow"); + } + } + + switch( eOp ) + { + case ocSep: + case ocClose: // pushed by the compiler + case ocMissing : ScMissing(); break; + case ocMacro : ScMacro(); break; + case ocDBArea : ScDBArea(); break; + case ocColRowNameAuto : ScColRowNameAuto(); break; + case ocIf : ScIfJump(); break; + case ocIfError : ScIfError( false ); break; + case ocIfNA : ScIfError( true ); break; + case ocChoose : ScChooseJump(); break; + case ocAdd : ScAdd(); break; + case ocSub : ScSub(); break; + case ocMul : ScMul(); break; + case ocDiv : ScDiv(); break; + case ocAmpersand : ScAmpersand(); break; + case ocPow : ScPow(); break; + case ocEqual : ScEqual(); break; + case ocNotEqual : ScNotEqual(); break; + case ocLess : ScLess(); break; + case ocGreater : ScGreater(); break; + case ocLessEqual : ScLessEqual(); break; + case ocGreaterEqual : ScGreaterEqual(); break; + case ocAnd : ScAnd(); break; + case ocOr : ScOr(); break; + case ocXor : ScXor(); break; + case ocIntersect : ScIntersect(); break; + case ocRange : ScRangeFunc(); break; + case ocUnion : ScUnionFunc(); break; + case ocNot : ScNot(); break; + case ocNegSub : + case ocNeg : ScNeg(); break; + case ocPercentSign : ScPercentSign(); break; + case ocPi : ScPi(); break; + case ocRandom : ScRandom(); break; + case ocRandomNV : ScRandom(); break; + case ocRandbetweenNV : ScRandbetween(); break; + case ocTrue : ScTrue(); break; + case ocFalse : ScFalse(); break; + case ocGetActDate : ScGetActDate(); break; + case ocGetActTime : ScGetActTime(); break; + case ocNotAvail : PushError( FormulaError::NotAvailable); break; + case ocDeg : ScDeg(); break; + case ocRad : ScRad(); break; + case ocSin : ScSin(); break; + case ocCos : ScCos(); break; + case ocTan : ScTan(); break; + case ocCot : ScCot(); break; + case ocArcSin : ScArcSin(); break; + case ocArcCos : ScArcCos(); break; + case ocArcTan : ScArcTan(); break; + case ocArcCot : ScArcCot(); break; + case ocSinHyp : ScSinHyp(); break; + case ocCosHyp : ScCosHyp(); break; + case ocTanHyp : ScTanHyp(); break; + case ocCotHyp : ScCotHyp(); break; + case ocArcSinHyp : ScArcSinHyp(); break; + case ocArcCosHyp : ScArcCosHyp(); break; + case ocArcTanHyp : ScArcTanHyp(); break; + case ocArcCotHyp : ScArcCotHyp(); break; + case ocCosecant : ScCosecant(); break; + case ocSecant : ScSecant(); break; + case ocCosecantHyp : ScCosecantHyp(); break; + case ocSecantHyp : ScSecantHyp(); break; + case ocExp : ScExp(); break; + case ocLn : ScLn(); break; + case ocLog10 : ScLog10(); break; + case ocSqrt : ScSqrt(); break; + case ocFact : ScFact(); break; + case ocGetYear : ScGetYear(); break; + case ocGetMonth : ScGetMonth(); break; + case ocGetDay : ScGetDay(); break; + case ocGetDayOfWeek : ScGetDayOfWeek(); break; + case ocWeek : ScGetWeekOfYear(); break; + case ocIsoWeeknum : ScGetIsoWeekOfYear(); break; + case ocWeeknumOOo : ScWeeknumOOo(); break; + case ocEasterSunday : ScEasterSunday(); break; + case ocNetWorkdays : ScNetWorkdays( false); break; + case ocNetWorkdays_MS : ScNetWorkdays( true ); break; + case ocWorkday_MS : ScWorkday_MS(); break; + case ocGetHour : ScGetHour(); break; + case ocGetMin : ScGetMin(); break; + case ocGetSec : ScGetSec(); break; + case ocPlusMinus : ScPlusMinus(); break; + case ocAbs : ScAbs(); break; + case ocInt : ScInt(); break; + case ocEven : ScEven(); break; + case ocOdd : ScOdd(); break; + case ocPhi : ScPhi(); break; + case ocGauss : ScGauss(); break; + case ocStdNormDist : ScStdNormDist(); break; + case ocStdNormDist_MS : ScStdNormDist_MS(); break; + case ocFisher : ScFisher(); break; + case ocFisherInv : ScFisherInv(); break; + case ocIsEmpty : ScIsEmpty(); break; + case ocIsString : ScIsString(); break; + case ocIsNonString : ScIsNonString(); break; + case ocIsLogical : ScIsLogical(); break; + case ocType : ScType(); break; + case ocCell : ScCell(); break; + case ocIsRef : ScIsRef(); break; + case ocIsValue : ScIsValue(); break; + case ocIsFormula : ScIsFormula(); break; + case ocFormula : ScFormula(); break; + case ocIsNA : ScIsNV(); break; + case ocIsErr : ScIsErr(); break; + case ocIsError : ScIsError(); break; + case ocIsEven : ScIsEven(); break; + case ocIsOdd : ScIsOdd(); break; + case ocN : ScN(); break; + case ocGetDateValue : ScGetDateValue(); break; + case ocGetTimeValue : ScGetTimeValue(); break; + case ocCode : ScCode(); break; + case ocTrim : ScTrim(); break; + case ocUpper : ScUpper(); break; + case ocProper : ScProper(); break; + case ocLower : ScLower(); break; + case ocLen : ScLen(); break; + case ocT : ScT(); break; + case ocClean : ScClean(); break; + case ocValue : ScValue(); break; + case ocNumberValue : ScNumberValue(); break; + case ocChar : ScChar(); break; + case ocArcTan2 : ScArcTan2(); break; + case ocMod : ScMod(); break; + case ocPower : ScPower(); break; + case ocRound : ScRound(); break; + case ocRoundSig : ScRoundSignificant(); break; + case ocRoundUp : ScRoundUp(); break; + case ocTrunc : + case ocRoundDown : ScRoundDown(); break; + case ocCeil : ScCeil( true ); break; + case ocCeil_MS : ScCeil_MS(); break; + case ocCeil_Precise : + case ocCeil_ISO : ScCeil_Precise(); break; + case ocCeil_Math : ScCeil( false ); break; + case ocFloor : ScFloor( true ); break; + case ocFloor_MS : ScFloor_MS(); break; + case ocFloor_Precise : ScFloor_Precise(); break; + case ocFloor_Math : ScFloor( false ); break; + case ocSumProduct : ScSumProduct(); break; + case ocSumSQ : ScSumSQ(); break; + case ocSumX2MY2 : ScSumX2MY2(); break; + case ocSumX2DY2 : ScSumX2DY2(); break; + case ocSumXMY2 : ScSumXMY2(); break; + case ocRawSubtract : ScRawSubtract(); break; + case ocLog : ScLog(); break; + case ocGCD : ScGCD(); break; + case ocLCM : ScLCM(); break; + case ocGetDate : ScGetDate(); break; + case ocGetTime : ScGetTime(); break; + case ocGetDiffDate : ScGetDiffDate(); break; + case ocGetDiffDate360 : ScGetDiffDate360(); break; + case ocGetDateDif : ScGetDateDif(); break; + case ocMin : ScMin() ; break; + case ocMinA : ScMin( true ); break; + case ocMax : ScMax(); break; + case ocMaxA : ScMax( true ); break; + case ocSum : ScSum(); break; + case ocProduct : ScProduct(); break; + case ocNPV : ScNPV(); break; + case ocIRR : ScIRR(); break; + case ocMIRR : ScMIRR(); break; + case ocISPMT : ScISPMT(); break; + case ocAverage : ScAverage() ; break; + case ocAverageA : ScAverage( true ); break; + case ocCount : ScCount(); break; + case ocCount2 : ScCount2(); break; + case ocVar : + case ocVarS : ScVar(); break; + case ocVarA : ScVar( true ); break; + case ocVarP : + case ocVarP_MS : ScVarP(); break; + case ocVarPA : ScVarP( true ); break; + case ocStDev : + case ocStDevS : ScStDev(); break; + case ocStDevA : ScStDev( true ); break; + case ocStDevP : + case ocStDevP_MS : ScStDevP(); break; + case ocStDevPA : ScStDevP( true ); break; + case ocPV : ScPV(); break; + case ocSYD : ScSYD(); break; + case ocDDB : ScDDB(); break; + case ocDB : ScDB(); break; + case ocVBD : ScVDB(); break; + case ocPDuration : ScPDuration(); break; + case ocSLN : ScSLN(); break; + case ocPMT : ScPMT(); break; + case ocColumns : ScColumns(); break; + case ocRows : ScRows(); break; + case ocSheets : ScSheets(); break; + case ocColumn : ScColumn(); break; + case ocRow : ScRow(); break; + case ocSheet : ScSheet(); break; + case ocRRI : ScRRI(); break; + case ocFV : ScFV(); break; + case ocNper : ScNper(); break; + case ocRate : ScRate(); break; + case ocFilterXML : ScFilterXML(); break; + case ocWebservice : ScWebservice(); break; + case ocEncodeURL : ScEncodeURL(); break; + case ocColor : ScColor(); break; + case ocErf_MS : ScErf(); break; + case ocErfc_MS : ScErfc(); break; + case ocIpmt : ScIpmt(); break; + case ocPpmt : ScPpmt(); break; + case ocCumIpmt : ScCumIpmt(); break; + case ocCumPrinc : ScCumPrinc(); break; + case ocEffect : ScEffect(); break; + case ocNominal : ScNominal(); break; + case ocSubTotal : ScSubTotal(); break; + case ocAggregate : ScAggregate(); break; + case ocDBSum : ScDBSum(); break; + case ocDBCount : ScDBCount(); break; + case ocDBCount2 : ScDBCount2(); break; + case ocDBAverage : ScDBAverage(); break; + case ocDBGet : ScDBGet(); break; + case ocDBMax : ScDBMax(); break; + case ocDBMin : ScDBMin(); break; + case ocDBProduct : ScDBProduct(); break; + case ocDBStdDev : ScDBStdDev(); break; + case ocDBStdDevP : ScDBStdDevP(); break; + case ocDBVar : ScDBVar(); break; + case ocDBVarP : ScDBVarP(); break; + case ocIndirect : ScIndirect(); break; + case ocAddress : ScAddressFunc(); break; + case ocMatch : ScMatch(); break; + case ocCountEmptyCells : ScCountEmptyCells(); break; + case ocCountIf : ScCountIf(); break; + case ocSumIf : ScSumIf(); break; + case ocAverageIf : ScAverageIf(); break; + case ocSumIfs : ScSumIfs(); break; + case ocAverageIfs : ScAverageIfs(); break; + case ocCountIfs : ScCountIfs(); break; + case ocLookup : ScLookup(); break; + case ocVLookup : ScVLookup(); break; + case ocHLookup : ScHLookup(); break; + case ocIndex : ScIndex(); break; + case ocMultiArea : ScMultiArea(); break; + case ocOffset : ScOffset(); break; + case ocAreas : ScAreas(); break; + case ocCurrency : ScCurrency(); break; + case ocReplace : ScReplace(); break; + case ocFixed : ScFixed(); break; + case ocFind : ScFind(); break; + case ocExact : ScExact(); break; + case ocLeft : ScLeft(); break; + case ocRight : ScRight(); break; + case ocSearch : ScSearch(); break; + case ocMid : ScMid(); break; + case ocText : ScText(); break; + case ocSubstitute : ScSubstitute(); break; + case ocRegex : ScRegex(); break; + case ocRept : ScRept(); break; + case ocConcat : ScConcat(); break; + case ocConcat_MS : ScConcat_MS(); break; + case ocTextJoin_MS : ScTextJoin_MS(); break; + case ocIfs_MS : ScIfs_MS(); break; + case ocSwitch_MS : ScSwitch_MS(); break; + case ocMinIfs_MS : ScMinIfs_MS(); break; + case ocMaxIfs_MS : ScMaxIfs_MS(); break; + case ocMatValue : ScMatValue(); break; + case ocMatrixUnit : ScEMat(); break; + case ocMatDet : ScMatDet(); break; + case ocMatInv : ScMatInv(); break; + case ocMatMult : ScMatMult(); break; + case ocMatTrans : ScMatTrans(); break; + case ocMatRef : ScMatRef(); break; + case ocB : ScB(); break; + case ocNormDist : ScNormDist( 3 ); break; + case ocNormDist_MS : ScNormDist( 4 ); break; + case ocExpDist : + case ocExpDist_MS : ScExpDist(); break; + case ocBinomDist : + case ocBinomDist_MS : ScBinomDist(); break; + case ocPoissonDist : ScPoissonDist( true ); break; + case ocPoissonDist_MS : ScPoissonDist( false ); break; + case ocCombin : ScCombin(); break; + case ocCombinA : ScCombinA(); break; + case ocPermut : ScPermut(); break; + case ocPermutationA : ScPermutationA(); break; + case ocHypGeomDist : ScHypGeomDist( 4 ); break; + case ocHypGeomDist_MS : ScHypGeomDist( 5 ); break; + case ocLogNormDist : ScLogNormDist( 1 ); break; + case ocLogNormDist_MS : ScLogNormDist( 4 ); break; + case ocTDist : ScTDist(); break; + case ocTDist_MS : ScTDist_MS(); break; + case ocTDist_RT : ScTDist_T( 1 ); break; + case ocTDist_2T : ScTDist_T( 2 ); break; + case ocFDist : + case ocFDist_RT : ScFDist(); break; + case ocFDist_LT : ScFDist_LT(); break; + case ocChiDist : ScChiDist( true ); break; + case ocChiDist_MS : ScChiDist( false ); break; + case ocChiSqDist : ScChiSqDist(); break; + case ocChiSqDist_MS : ScChiSqDist_MS(); break; + case ocStandard : ScStandard(); break; + case ocAveDev : ScAveDev(); break; + case ocDevSq : ScDevSq(); break; + case ocKurt : ScKurt(); break; + case ocSkew : ScSkew(); break; + case ocSkewp : ScSkewp(); break; + case ocModalValue : ScModalValue(); break; + case ocModalValue_MS : ScModalValue_MS( true ); break; + case ocModalValue_Multi : ScModalValue_MS( false ); break; + case ocMedian : ScMedian(); break; + case ocGeoMean : ScGeoMean(); break; + case ocHarMean : ScHarMean(); break; + case ocWeibull : + case ocWeibull_MS : ScWeibull(); break; + case ocBinomInv : + case ocCritBinom : ScCritBinom(); break; + case ocNegBinomVert : ScNegBinomDist(); break; + case ocNegBinomDist_MS : ScNegBinomDist_MS(); break; + case ocNoName : ScNoName(); break; + case ocBad : ScBadName(); break; + case ocZTest : + case ocZTest_MS : ScZTest(); break; + case ocTTest : + case ocTTest_MS : ScTTest(); break; + case ocFTest : + case ocFTest_MS : ScFTest(); break; + case ocRank : + case ocRank_Eq : ScRank( false ); break; + case ocRank_Avg : ScRank( true ); break; + case ocPercentile : + case ocPercentile_Inc : ScPercentile( true ); break; + case ocPercentile_Exc : ScPercentile( false ); break; + case ocPercentrank : + case ocPercentrank_Inc : ScPercentrank( true ); break; + case ocPercentrank_Exc : ScPercentrank( false ); break; + case ocLarge : ScLarge(); break; + case ocSmall : ScSmall(); break; + case ocFrequency : ScFrequency(); break; + case ocQuartile : + case ocQuartile_Inc : ScQuartile( true ); break; + case ocQuartile_Exc : ScQuartile( false ); break; + case ocNormInv : + case ocNormInv_MS : ScNormInv(); break; + case ocSNormInv : + case ocSNormInv_MS : ScSNormInv(); break; + case ocConfidence : + case ocConfidence_N : ScConfidence(); break; + case ocConfidence_T : ScConfidenceT(); break; + case ocTrimMean : ScTrimMean(); break; + case ocProb : ScProbability(); break; + case ocCorrel : ScCorrel(); break; + case ocCovar : + case ocCovarianceP : ScCovarianceP(); break; + case ocCovarianceS : ScCovarianceS(); break; + case ocPearson : ScPearson(); break; + case ocRSQ : ScRSQ(); break; + case ocSTEYX : ScSTEYX(); break; + case ocSlope : ScSlope(); break; + case ocIntercept : ScIntercept(); break; + case ocTrend : ScTrend(); break; + case ocGrowth : ScGrowth(); break; + case ocLinest : ScLinest(); break; + case ocLogest : ScLogest(); break; + case ocForecast_LIN : + case ocForecast : ScForecast(); break; + case ocForecast_ETS_ADD : ScForecast_Ets( etsAdd ); break; + case ocForecast_ETS_SEA : ScForecast_Ets( etsSeason ); break; + case ocForecast_ETS_MUL : ScForecast_Ets( etsMult ); break; + case ocForecast_ETS_PIA : ScForecast_Ets( etsPIAdd ); break; + case ocForecast_ETS_PIM : ScForecast_Ets( etsPIMult ); break; + case ocForecast_ETS_STA : ScForecast_Ets( etsStatAdd ); break; + case ocForecast_ETS_STM : ScForecast_Ets( etsStatMult ); break; + case ocGammaLn : + case ocGammaLn_MS : ScLogGamma(); break; + case ocGamma : ScGamma(); break; + case ocGammaDist : ScGammaDist( true ); break; + case ocGammaDist_MS : ScGammaDist( false ); break; + case ocGammaInv : + case ocGammaInv_MS : ScGammaInv(); break; + case ocChiTest : + case ocChiTest_MS : ScChiTest(); break; + case ocChiInv : + case ocChiInv_MS : ScChiInv(); break; + case ocChiSqInv : + case ocChiSqInv_MS : ScChiSqInv(); break; + case ocTInv : + case ocTInv_2T : ScTInv( 2 ); break; + case ocTInv_MS : ScTInv( 4 ); break; + case ocFInv : + case ocFInv_RT : ScFInv(); break; + case ocFInv_LT : ScFInv_LT(); break; + case ocLogInv : + case ocLogInv_MS : ScLogNormInv(); break; + case ocBetaDist : ScBetaDist(); break; + case ocBetaDist_MS : ScBetaDist_MS(); break; + case ocBetaInv : + case ocBetaInv_MS : ScBetaInv(); break; + case ocFourier : ScFourier(); break; + case ocExternal : ScExternal(); break; + case ocTableOp : ScTableOp(); break; + case ocStop : break; + case ocErrorType : ScErrorType(); break; + case ocErrorType_ODF : ScErrorType_ODF(); break; + case ocCurrent : ScCurrent(); break; + case ocStyle : ScStyle(); break; + case ocDde : ScDde(); break; + case ocBase : ScBase(); break; + case ocDecimal : ScDecimal(); break; + case ocConvertOOo : ScConvertOOo(); break; + case ocEuroConvert : ScEuroConvert(); break; + case ocRoman : ScRoman(); break; + case ocArabic : ScArabic(); break; + case ocInfo : ScInfo(); break; + case ocHyperLink : ScHyperLink(); break; + case ocBahtText : ScBahtText(); break; + case ocGetPivotData : ScGetPivotData(); break; + case ocJis : ScJis(); break; + case ocAsc : ScAsc(); break; + case ocLenB : ScLenB(); break; + case ocRightB : ScRightB(); break; + case ocLeftB : ScLeftB(); break; + case ocMidB : ScMidB(); break; + case ocReplaceB : ScReplaceB(); break; + case ocFindB : ScFindB(); break; + case ocSearchB : ScSearchB(); break; + case ocUnicode : ScUnicode(); break; + case ocUnichar : ScUnichar(); break; + case ocBitAnd : ScBitAnd(); break; + case ocBitOr : ScBitOr(); break; + case ocBitXor : ScBitXor(); break; + case ocBitRshift : ScBitRshift(); break; + case ocBitLshift : ScBitLshift(); break; + case ocTTT : ScTTT(); break; + case ocDebugVar : ScDebugVar(); break; + case ocNone : nFuncFmtType = SvNumFormatType::UNDEFINED; break; + default : PushError( FormulaError::UnknownOpCode); break; + } + + // If the function pushed a subroutine as result, continue with + // execution of the subroutine. + if (sp > nStackBase && pStack[sp-1]->GetOpCode() == ocCall) + { + Pop(); continue; + } + + if (FormulaCompiler::IsOpCodeVolatile(eOp)) + meVolatileType = VOLATILE; + + // Remember result matrix in case it could be reused. + if (sp && GetStackType() == svMatrix) + maTokenMatrixMap.emplace(pCur, pStack[sp-1]); + + // outer function determines format of an expression + if ( nFuncFmtType != SvNumFormatType::UNDEFINED ) + { + nRetTypeExpr = nFuncFmtType; + // Inherit the format index for currency, date or time formats. + switch (nFuncFmtType) + { + case SvNumFormatType::CURRENCY: + case SvNumFormatType::DATE: + case SvNumFormatType::TIME: + case SvNumFormatType::DATETIME: + case SvNumFormatType::DURATION: + nRetIndexExpr = nFuncFmtIndex; + break; + default: + nRetIndexExpr = 0; + } + } + } + + // Need a clean stack environment for the JumpMatrix to work. + if (nGlobalError != FormulaError::NONE && eOp != ocPush && sp > nStackBase + 1) + { + // Not all functions pop all parameters in case an error is + // generated. Clean up stack. Assumes that every function pushes a + // result, may be arbitrary in case of error. + FormulaConstTokenRef xLocalResult = pStack[ sp - 1 ]; + while (sp > nStackBase) + Pop(); + PushTokenRef( xLocalResult ); + } + + bool bGotResult; + do + { + bGotResult = false; + sal_uInt8 nLevel = 0; + if ( GetStackType( ++nLevel ) == svJumpMatrix ) + ; // nothing + else if ( GetStackType( ++nLevel ) == svJumpMatrix ) + ; // nothing + else + nLevel = 0; + if ( nLevel == 1 || (nLevel == 2 && aCode.IsEndOfPath()) ) + { + if (nLevel == 1) + aErrorFunctionStack.push_back( nErrorFunction); + bGotResult = JumpMatrix( nLevel ); + if (aErrorFunctionStack.empty()) + assert(!"ScInterpreter::Interpret - aErrorFunctionStack empty in JumpMatrix context"); + else + { + nErrorFunction = aErrorFunctionStack.back(); + if (bGotResult) + aErrorFunctionStack.pop_back(); + } + } + else + pJumpMatrix = nullptr; + } while ( bGotResult ); + + if( IsErrFunc(eOp) ) + ++nErrorFunction; + + if ( nGlobalError != FormulaError::NONE ) + { + if ( !nErrorFunctionCount ) + { // count of errorcode functions in formula + FormulaTokenArrayPlainIterator aIter(*pArr); + for ( FormulaToken* t = aIter.FirstRPN(); t; t = aIter.NextRPN() ) + { + if ( IsErrFunc(t->GetOpCode()) ) + ++nErrorFunctionCount; + } + } + if ( nErrorFunction >= nErrorFunctionCount ) + ++nErrorFunction; // that's it, error => terminate + else if (nErrorFunctionCount && sp && GetStackType() == svError) + { + // Clear global error if we have an individual error result, so + // an error evaluating function can receive multiple arguments + // and not all evaluated arguments inheriting the error. + // This is important for at least IFS() and SWITCH() as long as + // they are classified as error evaluating functions and not + // implemented as short-cutting jump code paths, but also for + // more than one evaluated argument to AGGREGATE() or COUNT() + // that may ignore errors. + nGlobalError = FormulaError::NONE; + } + } + } + + // End: obtain result + + bool bForcedResultType; + switch (eOp) + { + case ocGetDateValue: + case ocGetTimeValue: + // Force final result of DATEVALUE and TIMEVALUE to number type, + // which so far was date or time for calculations. + nRetTypeExpr = nFuncFmtType = SvNumFormatType::NUMBER; + nRetIndexExpr = nFuncFmtIndex = 0; + bForcedResultType = true; + break; + default: + bForcedResultType = false; + } + + if (sp == 1) + { + pCur = pStack[ sp-1 ]; + if( pCur->GetOpCode() == ocPush ) + { + // An svRefList can be resolved if it a) contains just one + // reference, or b) in array context contains an array of single + // cell references. + if (pCur->GetType() == svRefList) + { + PopRefListPushMatrixOrRef(); + pCur = pStack[ sp-1 ]; + } + switch( pCur->GetType() ) + { + case svEmptyCell: + ; // nothing + break; + case svError: + nGlobalError = pCur->GetError(); + break; + case svDouble : + { + // If typed, pop token to obtain type information and + // push a plain untyped double so the result token to + // be transferred to the formula cell result does not + // unnecessarily duplicate the information. + if (pCur->GetDoubleType() != 0) + { + double fVal = PopDouble(); + if (!bForcedResultType) + { + if (nCurFmtType != nFuncFmtType) + nRetIndexExpr = 0; // carry format index only for matching type + nRetTypeExpr = nFuncFmtType = nCurFmtType; + } + if (nRetTypeExpr == SvNumFormatType::DURATION) + { + // Round the duration in case a wall clock time + // display format is used instead of a duration + // format. To micro seconds which then catches + // the converted hh:mm:ss.9999997 cases. + if (fVal != 0.0) + { + fVal *= 86400.0; + fVal = rtl::math::round( fVal, 6); + fVal /= 86400.0; + } + } + PushTempToken( CreateFormulaDoubleToken( fVal)); + } + if ( nFuncFmtType == SvNumFormatType::UNDEFINED ) + { + nRetTypeExpr = SvNumFormatType::NUMBER; + nRetIndexExpr = 0; + } + } + break; + case svString : + nRetTypeExpr = SvNumFormatType::TEXT; + nRetIndexExpr = 0; + break; + case svSingleRef : + { + ScAddress aAdr; + PopSingleRef( aAdr ); + if( nGlobalError == FormulaError::NONE) + PushCellResultToken( false, aAdr, &nRetTypeExpr, &nRetIndexExpr, true); + } + break; + case svRefList : + PopError(); // maybe #REF! takes precedence over #VALUE! + PushError( FormulaError::NoValue); + break; + case svDoubleRef : + { + if ( bMatrixFormula ) + { // create matrix for {=A1:A5} + PopDoubleRefPushMatrix(); + ScMatrixRef xMat = PopMatrix(); + QueryMatrixType(xMat, nRetTypeExpr, nRetIndexExpr); + } + else + { + ScRange aRange; + PopDoubleRef( aRange ); + ScAddress aAdr; + if ( nGlobalError == FormulaError::NONE && DoubleRefToPosSingleRef( aRange, aAdr)) + PushCellResultToken( false, aAdr, &nRetTypeExpr, &nRetIndexExpr, true); + } + } + break; + case svExternalDoubleRef: + { + ScMatrixRef xMat; + PopExternalDoubleRef(xMat); + QueryMatrixType(xMat, nRetTypeExpr, nRetIndexExpr); + } + break; + case svMatrix : + { + sc::RangeMatrix aMat = PopRangeMatrix(); + if (aMat.isRangeValid()) + { + // This matrix represents a range reference. Apply implicit intersection. + double fVal = applyImplicitIntersection(aMat, aPos); + if (std::isnan(fVal)) + PushNoValue(); + else + PushInt(fVal); + } + else + // This is a normal matrix. + QueryMatrixType(aMat.mpMat, nRetTypeExpr, nRetIndexExpr); + } + break; + case svExternalSingleRef: + { + FormulaTokenRef xToken; + ScExternalRefCache::CellFormat aFmt; + PopExternalSingleRef(xToken, &aFmt); + if (nGlobalError != FormulaError::NONE) + break; + + PushTokenRef(xToken); + + if (aFmt.mbIsSet) + { + nFuncFmtType = aFmt.mnType; + nFuncFmtIndex = aFmt.mnIndex; + } + } + break; + default : + SetError( FormulaError::UnknownStackVariable); + } + } + else + SetError( FormulaError::UnknownStackVariable); + } + else if (sp > 1) + SetError( FormulaError::OperatorExpected); + else + SetError( FormulaError::NoCode); + + if (bForcedResultType || nRetTypeExpr != SvNumFormatType::UNDEFINED) + { + nRetFmtType = nRetTypeExpr; + nRetFmtIndex = nRetIndexExpr; + } + else if( nFuncFmtType != SvNumFormatType::UNDEFINED ) + { + nRetFmtType = nFuncFmtType; + nRetFmtIndex = nFuncFmtIndex; + } + else + nRetFmtType = SvNumFormatType::NUMBER; + + if (nGlobalError != FormulaError::NONE && GetStackType() != svError ) + PushError( nGlobalError); + + // THE final result. + xResult = PopToken(); + if (!xResult) + xResult = new FormulaErrorToken( FormulaError::UnknownStackVariable); + + // release tokens in expression stack + const FormulaToken** p = pStack; + while( maxsp-- ) + (*p++)->DecRef(); + + StackVar eType = xResult->GetType(); + if (eType == svMatrix) + // Results are immutable in case they would be reused as input for new + // interpreters. + xResult->GetMatrix()->SetImmutable(); + return eType; +} + +void ScInterpreter::AssertFormulaMatrix() +{ + bMatrixFormula = true; +} + +const svl::SharedString & ScInterpreter::GetStringResult() const +{ + return xResult->GetString(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/interpr5.cxx b/sc/source/core/tool/interpr5.cxx new file mode 100644 index 0000000000..ae499ec492 --- /dev/null +++ b/sc/source/core/tool/interpr5.cxx @@ -0,0 +1,3303 @@ +/* -*- 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 <rtl/math.hxx> +#include <string.h> +#include <math.h> + +#ifdef DEBUG_SC_LUP_DECOMPOSITION +#include <stdio.h> +#endif + +#include <unotools/bootstrap.hxx> +#include <svl/zforlist.hxx> +#include <tools/duration.hxx> + +#include <interpre.hxx> +#include <global.hxx> +#include <formulacell.hxx> +#include <document.hxx> +#include <dociter.hxx> +#include <scmatrix.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <cellkeytranslator.hxx> +#include <formulagroup.hxx> +#include <vcl/svapp.hxx> //Application:: + +#include <vector> + +using ::std::vector; +using namespace formula; + +namespace { + +double MatrixAdd(const double& lhs, const double& rhs) +{ + return ::rtl::math::approxAdd( lhs,rhs); +} + +double MatrixSub(const double& lhs, const double& rhs) +{ + return ::rtl::math::approxSub( lhs,rhs); +} + +double MatrixMul(const double& lhs, const double& rhs) +{ + return lhs * rhs; +} + +double MatrixDiv(const double& lhs, const double& rhs) +{ + return ScInterpreter::div( lhs,rhs); +} + +double MatrixPow(const double& lhs, const double& rhs) +{ + return ::pow( lhs,rhs); +} + +// Multiply n x m Mat A with m x l Mat B to n x l Mat R +void lcl_MFastMult(const ScMatrixRef& pA, const ScMatrixRef& pB, const ScMatrixRef& pR, + SCSIZE n, SCSIZE m, SCSIZE l) +{ + for (SCSIZE row = 0; row < n; row++) + { + for (SCSIZE col = 0; col < l; col++) + { // result element(col, row) =sum[ (row of A) * (column of B)] + KahanSum fSum = 0.0; + for (SCSIZE k = 0; k < m; k++) + fSum += pA->GetDouble(k,row) * pB->GetDouble(col,k); + pR->PutDouble(fSum.get(), col, row); + } + } +} + +} + +double ScInterpreter::ScGetGCD(double fx, double fy) +{ + // By ODFF definition GCD(0,a) => a. This is also vital for the code in + // ScGCD() to work correctly with a preset fy=0.0 + if (fy == 0.0) + return fx; + else if (fx == 0.0) + return fy; + else + { + double fz = fmod(fx, fy); + while (fz > 0.0) + { + fx = fy; + fy = fz; + fz = fmod(fx, fy); + } + return fy; + } +} + +void ScInterpreter::ScGCD() +{ + short nParamCount = GetByte(); + if ( !MustHaveParamCountMin( nParamCount, 1 ) ) + return; + + double fx, fy = 0.0; + ScRange aRange; + size_t nRefInList = 0; + while (nGlobalError == FormulaError::NONE && nParamCount-- > 0) + { + switch (GetStackType()) + { + case svDouble : + case svString: + case svSingleRef: + { + fx = ::rtl::math::approxFloor( GetDouble()); + if (fx < 0.0) + { + PushIllegalArgument(); + return; + } + fy = ScGetGCD(fx, fy); + } + break; + case svDoubleRef : + case svRefList : + { + FormulaError nErr = FormulaError::NONE; + PopDoubleRef( aRange, nParamCount, nRefInList); + double nCellVal; + ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags ); + if (aValIter.GetFirst(nCellVal, nErr)) + { + do + { + fx = ::rtl::math::approxFloor( nCellVal); + if (fx < 0.0) + { + PushIllegalArgument(); + return; + } + fy = ScGetGCD(fx, fy); + } while (nErr == FormulaError::NONE && aValIter.GetNext(nCellVal, nErr)); + } + SetError(nErr); + } + break; + case svMatrix : + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatrixRef pMat = GetMatrix(); + if (pMat) + { + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + if (nC == 0 || nR == 0) + SetError(FormulaError::IllegalArgument); + else + { + double nVal = pMat->GetGcd(); + fy = ScGetGCD(nVal,fy); + } + } + } + break; + default : SetError(FormulaError::IllegalParameter); break; + } + } + PushDouble(fy); +} + +void ScInterpreter:: ScLCM() +{ + short nParamCount = GetByte(); + if ( !MustHaveParamCountMin( nParamCount, 1 ) ) + return; + + double fx, fy = 1.0; + ScRange aRange; + size_t nRefInList = 0; + while (nGlobalError == FormulaError::NONE && nParamCount-- > 0) + { + switch (GetStackType()) + { + case svDouble : + case svString: + case svSingleRef: + { + fx = ::rtl::math::approxFloor( GetDouble()); + if (fx < 0.0) + { + PushIllegalArgument(); + return; + } + if (fx == 0.0 || fy == 0.0) + fy = 0.0; + else + fy = fx * fy / ScGetGCD(fx, fy); + } + break; + case svDoubleRef : + case svRefList : + { + FormulaError nErr = FormulaError::NONE; + PopDoubleRef( aRange, nParamCount, nRefInList); + double nCellVal; + ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags ); + if (aValIter.GetFirst(nCellVal, nErr)) + { + do + { + fx = ::rtl::math::approxFloor( nCellVal); + if (fx < 0.0) + { + PushIllegalArgument(); + return; + } + if (fx == 0.0 || fy == 0.0) + fy = 0.0; + else + fy = fx * fy / ScGetGCD(fx, fy); + } while (nErr == FormulaError::NONE && aValIter.GetNext(nCellVal, nErr)); + } + SetError(nErr); + } + break; + case svMatrix : + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatrixRef pMat = GetMatrix(); + if (pMat) + { + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + if (nC == 0 || nR == 0) + SetError(FormulaError::IllegalArgument); + else + { + double nVal = pMat->GetLcm(); + fy = (nVal * fy ) / ScGetGCD(nVal, fy); + } + } + } + break; + default : SetError(FormulaError::IllegalParameter); break; + } + } + PushDouble(fy); +} + +void ScInterpreter::MakeMatNew(ScMatrixRef& rMat, SCSIZE nC, SCSIZE nR) +{ + rMat->SetErrorInterpreter( this); + // A temporary matrix is mutable and ScMatrix::CloneIfConst() returns the + // very matrix. + rMat->SetMutable(); + SCSIZE nCols, nRows; + rMat->GetDimensions( nCols, nRows); + if ( nCols != nC || nRows != nR ) + { // arbitrary limit of elements exceeded + SetError( FormulaError::MatrixSize); + rMat.reset(); + } +} + +ScMatrixRef ScInterpreter::GetNewMat(SCSIZE nC, SCSIZE nR, bool bEmpty) +{ + ScMatrixRef pMat; + if (bEmpty) + pMat = new ScMatrix(nC, nR); + else + pMat = new ScMatrix(nC, nR, 0.0); + MakeMatNew(pMat, nC, nR); + return pMat; +} + +ScMatrixRef ScInterpreter::GetNewMat(SCSIZE nC, SCSIZE nR, const std::vector<double>& rValues) +{ + ScMatrixRef pMat(new ScMatrix(nC, nR, rValues)); + MakeMatNew(pMat, nC, nR); + return pMat; +} + +ScMatrixRef ScInterpreter::CreateMatrixFromDoubleRef( const FormulaToken* pToken, + SCCOL nCol1, SCROW nRow1, SCTAB nTab1, + SCCOL nCol2, SCROW nRow2, SCTAB nTab2 ) +{ + if (nTab1 != nTab2 || nGlobalError != FormulaError::NONE) + { + // Not a 2D matrix. + SetError(FormulaError::IllegalParameter); + return nullptr; + } + + if (nTab1 == nTab2 && pToken) + { + const ScComplexRefData& rCRef = *pToken->GetDoubleRef(); + if (rCRef.IsTrimToData()) + { + // Clamp the size of the matrix area to rows which actually contain data. + // For e.g. SUM(IF over an entire column, this can make a big difference, + // But lets not trim the empty space from the top or left as this matters + // at least in matrix formulas involving IF(). + // Refer ScCompiler::AnnotateTrimOnDoubleRefs() where double-refs are + // flagged for trimming. + SCCOL nTempCol = nCol1; + SCROW nTempRow = nRow1; + mrDoc.ShrinkToDataArea(nTab1, nTempCol, nTempRow, nCol2, nRow2); + } + } + + SCSIZE nMatCols = static_cast<SCSIZE>(nCol2 - nCol1 + 1); + SCSIZE nMatRows = static_cast<SCSIZE>(nRow2 - nRow1 + 1); + + if (!ScMatrix::IsSizeAllocatable( nMatCols, nMatRows)) + { + SetError(FormulaError::MatrixSize); + return nullptr; + } + + ScTokenMatrixMap::const_iterator aIter; + if (pToken && ((aIter = maTokenMatrixMap.find( pToken)) != maTokenMatrixMap.end())) + { + /* XXX casting const away here is ugly; ScMatrixToken (to which the + * result of this function usually is assigned) should not be forced to + * carry a ScConstMatrixRef though. + * TODO: a matrix already stored in pTokenMatrixMap should be + * read-only and have a copy-on-write mechanism. Previously all tokens + * were modifiable so we're already better than before ... */ + return const_cast<FormulaToken*>((*aIter).second.get())->GetMatrix(); + } + + ScMatrixRef pMat = GetNewMat( nMatCols, nMatRows, true); + if (!pMat || nGlobalError != FormulaError::NONE) + return nullptr; + + if (!bCalcAsShown) + { + // Use fast array fill. + mrDoc.FillMatrix(*pMat, nTab1, nCol1, nRow1, nCol2, nRow2); + } + else + { + // Use slower ScCellIterator to round values. + + // TODO: this probably could use CellBucket for faster storage, see + // sc/source/core/data/column2.cxx and FillMatrixHandler, and then be + // moved to a function on its own, and/or squeeze the rounding into a + // similar FillMatrixHandler that would need to keep track of the cell + // position then. + + // Set position where the next entry is expected. + SCROW nNextRow = nRow1; + SCCOL nNextCol = nCol1; + // Set last position as if there was a previous entry. + SCROW nThisRow = nRow2; + SCCOL nThisCol = nCol1 - 1; + + ScCellIterator aCellIter( mrDoc, ScRange( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2)); + for (bool bHas = aCellIter.first(); bHas; bHas = aCellIter.next()) + { + nThisCol = aCellIter.GetPos().Col(); + nThisRow = aCellIter.GetPos().Row(); + if (nThisCol != nNextCol || nThisRow != nNextRow) + { + // Fill empty between iterator's positions. + for ( ; nNextCol <= nThisCol; ++nNextCol) + { + const SCSIZE nC = nNextCol - nCol1; + const SCSIZE nMatStopRow = ((nNextCol < nThisCol) ? nMatRows : nThisRow - nRow1); + for (SCSIZE nR = nNextRow - nRow1; nR < nMatStopRow; ++nR) + { + pMat->PutEmpty( nC, nR); + } + nNextRow = nRow1; + } + } + if (nThisRow == nRow2) + { + nNextCol = nThisCol + 1; + nNextRow = nRow1; + } + else + { + nNextCol = nThisCol; + nNextRow = nThisRow + 1; + } + + const SCSIZE nMatCol = static_cast<SCSIZE>(nThisCol - nCol1); + const SCSIZE nMatRow = static_cast<SCSIZE>(nThisRow - nRow1); + ScRefCellValue aCell( aCellIter.getRefCellValue()); + if (aCellIter.isEmpty() || aCell.hasEmptyValue()) + { + pMat->PutEmpty( nMatCol, nMatRow); + } + else if (aCell.hasError()) + { + pMat->PutError( aCell.getFormula()->GetErrCode(), nMatCol, nMatRow); + } + else if (aCell.hasNumeric()) + { + double fVal = aCell.getValue(); + // CELLTYPE_FORMULA already stores the rounded value. + if (aCell.getType() == CELLTYPE_VALUE) + { + // TODO: this could be moved to ScCellIterator to take + // advantage of the faster ScAttrArray_IterGetNumberFormat. + const ScAddress aAdr( nThisCol, nThisRow, nTab1); + const sal_uInt32 nNumFormat = mrDoc.GetNumberFormat( mrContext, aAdr); + fVal = mrDoc.RoundValueAsShown( fVal, nNumFormat, &mrContext); + } + pMat->PutDouble( fVal, nMatCol, nMatRow); + } + else if (aCell.hasString()) + { + pMat->PutString( mrStrPool.intern( aCell.getString(&mrDoc)), nMatCol, nMatRow); + } + else + { + assert(!"aCell.what?"); + pMat->PutEmpty( nMatCol, nMatRow); + } + } + + // Fill empty if iterator's last position wasn't the end. + if (nThisCol != nCol2 || nThisRow != nRow2) + { + for ( ; nNextCol <= nCol2; ++nNextCol) + { + SCSIZE nC = nNextCol - nCol1; + for (SCSIZE nR = nNextRow - nRow1; nR < nMatRows; ++nR) + { + pMat->PutEmpty( nC, nR); + } + nNextRow = nRow1; + } + } + } + + if (pToken) + maTokenMatrixMap.emplace(pToken, new ScMatrixToken( pMat)); + + return pMat; +} + +ScMatrixRef ScInterpreter::GetMatrix() +{ + ScMatrixRef pMat = nullptr; + switch (GetRawStackType()) + { + case svSingleRef : + { + ScAddress aAdr; + PopSingleRef( aAdr ); + pMat = GetNewMat(1, 1); + if (pMat) + { + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasEmptyValue()) + pMat->PutEmpty(0, 0); + else if (aCell.hasNumeric()) + pMat->PutDouble(GetCellValue(aAdr, aCell), 0); + else + { + svl::SharedString aStr; + GetCellString(aStr, aCell); + pMat->PutString(aStr, 0); + } + } + } + break; + case svDoubleRef: + { + SCCOL nCol1, nCol2; + SCROW nRow1, nRow2; + SCTAB nTab1, nTab2; + const formula::FormulaToken* p = sp ? pStack[sp-1] : nullptr; + PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + pMat = CreateMatrixFromDoubleRef( p, nCol1, nRow1, nTab1, + nCol2, nRow2, nTab2); + } + break; + case svMatrix: + pMat = PopMatrix(); + break; + case svError : + case svMissing : + case svDouble : + { + double fVal = GetDouble(); + pMat = GetNewMat( 1, 1); + if ( pMat ) + { + if ( nGlobalError != FormulaError::NONE ) + { + fVal = CreateDoubleError( nGlobalError); + nGlobalError = FormulaError::NONE; + } + pMat->PutDouble( fVal, 0); + } + } + break; + case svString : + { + svl::SharedString aStr = GetString(); + pMat = GetNewMat( 1, 1); + if ( pMat ) + { + if ( nGlobalError != FormulaError::NONE ) + { + double fVal = CreateDoubleError( nGlobalError); + pMat->PutDouble( fVal, 0); + nGlobalError = FormulaError::NONE; + } + else + pMat->PutString(aStr, 0); + } + } + break; + case svExternalSingleRef: + { + ScExternalRefCache::TokenRef pToken; + PopExternalSingleRef(pToken); + pMat = GetNewMat( 1, 1, true); + if (!pMat) + break; + if (nGlobalError != FormulaError::NONE) + { + pMat->PutError( nGlobalError, 0, 0); + nGlobalError = FormulaError::NONE; + break; + } + switch (pToken->GetType()) + { + case svError: + pMat->PutError( pToken->GetError(), 0, 0); + break; + case svDouble: + pMat->PutDouble( pToken->GetDouble(), 0, 0); + break; + case svString: + pMat->PutString( pToken->GetString(), 0, 0); + break; + default: + ; // nothing, empty element matrix + } + } + break; + case svExternalDoubleRef: + PopExternalDoubleRef(pMat); + break; + default: + PopError(); + SetError( FormulaError::IllegalArgument); + break; + } + return pMat; +} + +ScMatrixRef ScInterpreter::GetMatrix( short & rParam, size_t & rRefInList ) +{ + switch (GetRawStackType()) + { + case svRefList: + { + ScRange aRange( ScAddress::INITIALIZE_INVALID ); + PopDoubleRef( aRange, rParam, rRefInList); + if (nGlobalError != FormulaError::NONE) + return nullptr; + + SCCOL nCol1, nCol2; + SCROW nRow1, nRow2; + SCTAB nTab1, nTab2; + aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + return CreateMatrixFromDoubleRef( nullptr, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + } + break; + default: + return GetMatrix(); + } +} + +sc::RangeMatrix ScInterpreter::GetRangeMatrix() +{ + sc::RangeMatrix aRet; + switch (GetRawStackType()) + { + case svMatrix: + aRet = PopRangeMatrix(); + break; + default: + aRet.mpMat = GetMatrix(); + } + return aRet; +} + +void ScInterpreter::ScMatValue() +{ + if ( !MustHaveParamCount( GetByte(), 3 ) ) + return; + + // 0 to count-1 + // Theoretically we could have GetSize() instead of GetUInt32(), but + // really, practically ... + SCSIZE nR = static_cast<SCSIZE>(GetUInt32()); + SCSIZE nC = static_cast<SCSIZE>(GetUInt32()); + if (nGlobalError != FormulaError::NONE) + { + PushError( nGlobalError); + return; + } + switch (GetStackType()) + { + case svSingleRef : + { + ScAddress aAdr; + PopSingleRef( aAdr ); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.getType() == CELLTYPE_FORMULA) + { + FormulaError nErrCode = aCell.getFormula()->GetErrCode(); + if (nErrCode != FormulaError::NONE) + PushError( nErrCode); + else + { + const ScMatrix* pMat = aCell.getFormula()->GetMatrix(); + CalculateMatrixValue(pMat,nC,nR); + } + } + else + PushIllegalParameter(); + } + break; + case svDoubleRef : + { + SCCOL nCol1; + SCROW nRow1; + SCTAB nTab1; + SCCOL nCol2; + SCROW nRow2; + SCTAB nTab2; + PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + if (nCol2 - nCol1 >= static_cast<SCCOL>(nR) && + nRow2 - nRow1 >= static_cast<SCROW>(nC) && + nTab1 == nTab2) + { + ScAddress aAdr( sal::static_int_cast<SCCOL>( nCol1 + nR ), + sal::static_int_cast<SCROW>( nRow1 + nC ), nTab1 ); + ScRefCellValue aCell(mrDoc, aAdr); + if (aCell.hasNumeric()) + PushDouble(GetCellValue(aAdr, aCell)); + else + { + svl::SharedString aStr; + GetCellString(aStr, aCell); + PushString(aStr); + } + } + else + PushNoValue(); + } + break; + case svMatrix: + { + ScMatrixRef pMat = PopMatrix(); + CalculateMatrixValue(pMat.get(),nC,nR); + } + break; + default: + PopError(); + PushIllegalParameter(); + break; + } +} +void ScInterpreter::CalculateMatrixValue(const ScMatrix* pMat,SCSIZE nC,SCSIZE nR) +{ + if (pMat) + { + SCSIZE nCl, nRw; + pMat->GetDimensions(nCl, nRw); + if (nC < nCl && nR < nRw) + { + const ScMatrixValue nMatVal = pMat->Get( nC, nR); + ScMatValType nMatValType = nMatVal.nType; + if (ScMatrix::IsNonValueType( nMatValType)) + PushString( nMatVal.GetString() ); + else + PushDouble(nMatVal.fVal); + // also handles DoubleError + } + else + PushNoValue(); + } + else + PushNoValue(); +} + +void ScInterpreter::ScEMat() +{ + if ( !MustHaveParamCount( GetByte(), 1 ) ) + return; + + SCSIZE nDim = static_cast<SCSIZE>(GetUInt32()); + if (nGlobalError != FormulaError::NONE || nDim == 0) + PushIllegalArgument(); + else if (!ScMatrix::IsSizeAllocatable( nDim, nDim)) + PushError( FormulaError::MatrixSize); + else + { + ScMatrixRef pRMat = GetNewMat(nDim, nDim, /*bEmpty*/true); + if (pRMat) + { + MEMat(pRMat, nDim); + PushMatrix(pRMat); + } + else + PushIllegalArgument(); + } +} + +void ScInterpreter::MEMat(const ScMatrixRef& mM, SCSIZE n) +{ + mM->FillDouble(0.0, 0, 0, n-1, n-1); + for (SCSIZE i = 0; i < n; i++) + mM->PutDouble(1.0, i, i); +} + +/* Matrix LUP decomposition according to the pseudocode of "Introduction to + * Algorithms" by Cormen, Leiserson, Rivest, Stein. + * + * Added scaling for numeric stability. + * + * Given an n x n nonsingular matrix A, find a permutation matrix P, a unit + * lower-triangular matrix L, and an upper-triangular matrix U such that PA=LU. + * Compute L and U "in place" in the matrix A, the original content is + * destroyed. Note that the diagonal elements of the U triangular matrix + * replace the diagonal elements of the L-unit matrix (that are each ==1). The + * permutation matrix P is an array, where P[i]=j means that the i-th row of P + * contains a 1 in column j. Additionally keep track of the number of + * permutations (row exchanges). + * + * Returns 0 if a singular matrix is encountered, else +1 if an even number of + * permutations occurred, or -1 if odd, which is the sign of the determinant. + * This may be used to calculate the determinant by multiplying the sign with + * the product of the diagonal elements of the LU matrix. + */ +static int lcl_LUP_decompose( ScMatrix* mA, const SCSIZE n, + ::std::vector< SCSIZE> & P ) +{ + int nSign = 1; + // Find scale of each row. + ::std::vector< double> aScale(n); + for (SCSIZE i=0; i < n; ++i) + { + double fMax = 0.0; + for (SCSIZE j=0; j < n; ++j) + { + double fTmp = fabs( mA->GetDouble( j, i)); + if (fMax < fTmp) + fMax = fTmp; + } + if (fMax == 0.0) + return 0; // singular matrix + aScale[i] = 1.0 / fMax; + } + // Represent identity permutation, P[i]=i + for (SCSIZE i=0; i < n; ++i) + P[i] = i; + // "Recursion" on the diagonal. + SCSIZE l = n - 1; + for (SCSIZE k=0; k < l; ++k) + { + // Implicit pivoting. With the scale found for a row, compare values of + // a column and pick largest. + double fMax = 0.0; + double fScale = aScale[k]; + SCSIZE kp = k; + for (SCSIZE i = k; i < n; ++i) + { + double fTmp = fScale * fabs( mA->GetDouble( k, i)); + if (fMax < fTmp) + { + fMax = fTmp; + kp = i; + } + } + if (fMax == 0.0) + return 0; // singular matrix + // Swap rows. The pivot element will be at mA[k,kp] (row,col notation) + if (k != kp) + { + // permutations + SCSIZE nTmp = P[k]; + P[k] = P[kp]; + P[kp] = nTmp; + nSign = -nSign; + // scales + double fTmp = aScale[k]; + aScale[k] = aScale[kp]; + aScale[kp] = fTmp; + // elements + for (SCSIZE i=0; i < n; ++i) + { + double fMatTmp = mA->GetDouble( i, k); + mA->PutDouble( mA->GetDouble( i, kp), i, k); + mA->PutDouble( fMatTmp, i, kp); + } + } + // Compute Schur complement. + for (SCSIZE i = k+1; i < n; ++i) + { + double fNum = mA->GetDouble( k, i); + double fDen = mA->GetDouble( k, k); + mA->PutDouble( fNum/fDen, k, i); + for (SCSIZE j = k+1; j < n; ++j) + mA->PutDouble( ( mA->GetDouble( j, i) * fDen - + fNum * mA->GetDouble( j, k) ) / fDen, j, i); + } + } +#ifdef DEBUG_SC_LUP_DECOMPOSITION + fprintf( stderr, "\n%s\n", "lcl_LUP_decompose(): LU"); + for (SCSIZE i=0; i < n; ++i) + { + for (SCSIZE j=0; j < n; ++j) + fprintf( stderr, "%8.2g ", mA->GetDouble( j, i)); + fprintf( stderr, "\n%s\n", ""); + } + fprintf( stderr, "\n%s\n", "lcl_LUP_decompose(): P"); + for (SCSIZE j=0; j < n; ++j) + fprintf( stderr, "%5u ", (unsigned)P[j]); + fprintf( stderr, "\n%s\n", ""); +#endif + + bool bSingular=false; + for (SCSIZE i=0; i<n && !bSingular; i++) + bSingular = (mA->GetDouble(i,i)) == 0.0; + if (bSingular) + nSign = 0; + + return nSign; +} + +/* Solve a LUP decomposed equation Ax=b. LU is a combined matrix of L and U + * triangulars and P the permutation vector as obtained from + * lcl_LUP_decompose(). B is the right-hand side input vector, X is used to + * return the solution vector. + */ +static void lcl_LUP_solve( const ScMatrix* mLU, const SCSIZE n, + const ::std::vector< SCSIZE> & P, const ::std::vector< double> & B, + ::std::vector< double> & X ) +{ + SCSIZE nFirst = SCSIZE_MAX; + // Ax=b => PAx=Pb, with decomposition LUx=Pb. + // Define y=Ux and solve for y in Ly=Pb using forward substitution. + for (SCSIZE i=0; i < n; ++i) + { + KahanSum fSum = B[P[i]]; + // Matrix inversion comes with a lot of zeros in the B vectors, we + // don't have to do all the computing with results multiplied by zero. + // Until then, simply lookout for the position of the first nonzero + // value. + if (nFirst != SCSIZE_MAX) + { + for (SCSIZE j = nFirst; j < i; ++j) + fSum -= mLU->GetDouble( j, i) * X[j]; // X[j] === y[j] + } + else if (fSum != 0) + nFirst = i; + X[i] = fSum.get(); // X[i] === y[i] + } + // Solve for x in Ux=y using back substitution. + for (SCSIZE i = n; i--; ) + { + KahanSum fSum = X[i]; // X[i] === y[i] + for (SCSIZE j = i+1; j < n; ++j) + fSum -= mLU->GetDouble( j, i) * X[j]; // X[j] === x[j] + X[i] = fSum.get() / mLU->GetDouble( i, i); // X[i] === x[i] + } +#ifdef DEBUG_SC_LUP_DECOMPOSITION + fprintf( stderr, "\n%s\n", "lcl_LUP_solve():"); + for (SCSIZE i=0; i < n; ++i) + fprintf( stderr, "%8.2g ", X[i]); + fprintf( stderr, "%s\n", ""); +#endif +} + +void ScInterpreter::ScMatDet() +{ + if ( !MustHaveParamCount( GetByte(), 1 ) ) + return; + + ScMatrixRef pMat = GetMatrix(); + if (!pMat) + { + PushIllegalParameter(); + return; + } + if ( !pMat->IsNumeric() ) + { + PushNoValue(); + return; + } + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + if ( nC != nR || nC == 0 ) + PushIllegalArgument(); + else if (!ScMatrix::IsSizeAllocatable( nC, nR)) + PushError( FormulaError::MatrixSize); + else + { + // LUP decomposition is done inplace, use copy. + ScMatrixRef xLU = pMat->Clone(); + if (!xLU) + PushError( FormulaError::CodeOverflow); + else + { + ::std::vector< SCSIZE> P(nR); + int nDetSign = lcl_LUP_decompose( xLU.get(), nR, P); + if (!nDetSign) + PushInt(0); // singular matrix + else + { + // In an LU matrix the determinant is simply the product of + // all diagonal elements. + double fDet = nDetSign; + for (SCSIZE i=0; i < nR; ++i) + fDet *= xLU->GetDouble( i, i); + PushDouble( fDet); + } + } + } +} + +void ScInterpreter::ScMatInv() +{ + if ( !MustHaveParamCount( GetByte(), 1 ) ) + return; + + ScMatrixRef pMat = GetMatrix(); + if (!pMat) + { + PushIllegalParameter(); + return; + } + if ( !pMat->IsNumeric() ) + { + PushNoValue(); + return; + } + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + + if (ScCalcConfig::isOpenCLEnabled()) + { + sc::FormulaGroupInterpreter *pInterpreter = sc::FormulaGroupInterpreter::getStatic(); + if (pInterpreter != nullptr) + { + ScMatrixRef xResMat = pInterpreter->inverseMatrix(*pMat); + if (xResMat) + { + PushMatrix(xResMat); + return; + } + } + } + + if ( nC != nR || nC == 0 ) + PushIllegalArgument(); + else if (!ScMatrix::IsSizeAllocatable( nC, nR)) + PushError( FormulaError::MatrixSize); + else + { + // LUP decomposition is done inplace, use copy. + ScMatrixRef xLU = pMat->Clone(); + // The result matrix. + ScMatrixRef xY = GetNewMat( nR, nR, /*bEmpty*/true ); + if (!xLU || !xY) + PushError( FormulaError::CodeOverflow); + else + { + ::std::vector< SCSIZE> P(nR); + int nDetSign = lcl_LUP_decompose( xLU.get(), nR, P); + if (!nDetSign) + PushIllegalArgument(); + else + { + // Solve equation for each column. + ::std::vector< double> B(nR); + ::std::vector< double> X(nR); + for (SCSIZE j=0; j < nR; ++j) + { + for (SCSIZE i=0; i < nR; ++i) + B[i] = 0.0; + B[j] = 1.0; + lcl_LUP_solve( xLU.get(), nR, P, B, X); + for (SCSIZE i=0; i < nR; ++i) + xY->PutDouble( X[i], j, i); + } +#ifdef DEBUG_SC_LUP_DECOMPOSITION + /* Possible checks for ill-condition: + * 1. Scale matrix, invert scaled matrix. If there are + * elements of the inverted matrix that are several + * orders of magnitude greater than 1 => + * ill-conditioned. + * Just how much is "several orders"? + * 2. Invert the inverted matrix and assess whether the + * result is sufficiently close to the original matrix. + * If not => ill-conditioned. + * Just what is sufficient? + * 3. Multiplying the inverse by the original matrix should + * produce a result sufficiently close to the identity + * matrix. + * Just what is sufficient? + * + * The following is #3. + */ + const double fInvEpsilon = 1.0E-7; + ScMatrixRef xR = GetNewMat( nR, nR); + if (xR) + { + ScMatrix* pR = xR.get(); + lcl_MFastMult( pMat, xY.get(), pR, nR, nR, nR); + fprintf( stderr, "\n%s\n", "ScMatInv(): mult-identity"); + for (SCSIZE i=0; i < nR; ++i) + { + for (SCSIZE j=0; j < nR; ++j) + { + double fTmp = pR->GetDouble( j, i); + fprintf( stderr, "%8.2g ", fTmp); + if (fabs( fTmp - (i == j)) > fInvEpsilon) + SetError( FormulaError::IllegalArgument); + } + fprintf( stderr, "\n%s\n", ""); + } + } +#endif + if (nGlobalError != FormulaError::NONE) + PushError( nGlobalError); + else + PushMatrix( xY); + } + } + } +} + +void ScInterpreter::ScMatMult() +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + ScMatrixRef pMat2 = GetMatrix(); + ScMatrixRef pMat1 = GetMatrix(); + ScMatrixRef pRMat; + if (pMat1 && pMat2) + { + if ( pMat1->IsNumeric() && pMat2->IsNumeric() ) + { + SCSIZE nC1, nC2; + SCSIZE nR1, nR2; + pMat1->GetDimensions(nC1, nR1); + pMat2->GetDimensions(nC2, nR2); + if (nC1 != nR2) + PushIllegalArgument(); + else + { + pRMat = GetNewMat(nC2, nR1, /*bEmpty*/true); + if (pRMat) + { + KahanSum fSum; + for (SCSIZE i = 0; i < nR1; i++) + { + for (SCSIZE j = 0; j < nC2; j++) + { + fSum = 0.0; + for (SCSIZE k = 0; k < nC1; k++) + { + fSum += pMat1->GetDouble(k,i)*pMat2->GetDouble(j,k); + } + pRMat->PutDouble(fSum.get(), j, i); + } + } + PushMatrix(pRMat); + } + else + PushIllegalArgument(); + } + } + else + PushNoValue(); + } + else + PushIllegalParameter(); +} + +void ScInterpreter::ScMatTrans() +{ + if ( !MustHaveParamCount( GetByte(), 1 ) ) + return; + + ScMatrixRef pMat = GetMatrix(); + ScMatrixRef pRMat; + if (pMat) + { + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + pRMat = GetNewMat(nR, nC, /*bEmpty*/true); + if ( pRMat ) + { + pMat->MatTrans(*pRMat); + PushMatrix(pRMat); + } + else + PushIllegalArgument(); + } + else + PushIllegalParameter(); +} + +/** Minimum extent of one result matrix dimension. + For a row or column vector to be replicated the larger matrix dimension is + returned, else the smaller dimension. + */ +static SCSIZE lcl_GetMinExtent( SCSIZE n1, SCSIZE n2 ) +{ + if (n1 == 1) + return n2; + else if (n2 == 1) + return n1; + else if (n1 < n2) + return n1; + else + return n2; +} + +static ScMatrixRef lcl_MatrixCalculation( + const ScMatrix& rMat1, const ScMatrix& rMat2, ScInterpreter* pInterpreter, ScMatrix::CalculateOpFunction Op) +{ + SCSIZE nC1, nC2, nMinC; + SCSIZE nR1, nR2, nMinR; + rMat1.GetDimensions(nC1, nR1); + rMat2.GetDimensions(nC2, nR2); + nMinC = lcl_GetMinExtent( nC1, nC2); + nMinR = lcl_GetMinExtent( nR1, nR2); + ScMatrixRef xResMat = pInterpreter->GetNewMat(nMinC, nMinR, /*bEmpty*/true); + if (xResMat) + xResMat->ExecuteBinaryOp(nMinC, nMinR, rMat1, rMat2, pInterpreter, Op); + return xResMat; +} + +ScMatrixRef ScInterpreter::MatConcat(const ScMatrixRef& pMat1, const ScMatrixRef& pMat2) +{ + SCSIZE nC1, nC2, nMinC; + SCSIZE nR1, nR2, nMinR; + pMat1->GetDimensions(nC1, nR1); + pMat2->GetDimensions(nC2, nR2); + nMinC = lcl_GetMinExtent( nC1, nC2); + nMinR = lcl_GetMinExtent( nR1, nR2); + ScMatrixRef xResMat = GetNewMat(nMinC, nMinR, /*bEmpty*/true); + if (xResMat) + { + xResMat->MatConcat(nMinC, nMinR, pMat1, pMat2, *pFormatter, mrDoc.GetSharedStringPool()); + } + return xResMat; +} + +// for DATE, TIME, DATETIME, DURATION +static void lcl_GetDiffDateTimeFmtType( SvNumFormatType& nFuncFmt, SvNumFormatType nFmt1, SvNumFormatType nFmt2 ) +{ + if ( nFmt1 == SvNumFormatType::UNDEFINED && nFmt2 == SvNumFormatType::UNDEFINED ) + return; + + if ( nFmt1 == nFmt2 ) + { + if ( nFmt1 == SvNumFormatType::TIME || nFmt1 == SvNumFormatType::DATETIME + || nFmt1 == SvNumFormatType::DURATION ) + nFuncFmt = SvNumFormatType::DURATION; // times result in time duration + // else: nothing special, number (date - date := days) + } + else if ( nFmt1 == SvNumFormatType::UNDEFINED ) + nFuncFmt = nFmt2; // e.g. date + days := date + else if ( nFmt2 == SvNumFormatType::UNDEFINED ) + nFuncFmt = nFmt1; + else + { + if ( nFmt1 == SvNumFormatType::DATE || nFmt2 == SvNumFormatType::DATE || + nFmt1 == SvNumFormatType::DATETIME || nFmt2 == SvNumFormatType::DATETIME ) + { + if ( nFmt1 == SvNumFormatType::TIME || nFmt2 == SvNumFormatType::TIME ) + nFuncFmt = SvNumFormatType::DATETIME; // date + time + } + } +} + +void ScInterpreter::ScAdd() +{ + CalculateAddSub(false); +} + +void ScInterpreter::CalculateAddSub(bool _bSub) +{ + ScMatrixRef pMat1 = nullptr; + ScMatrixRef pMat2 = nullptr; + double fVal1 = 0.0, fVal2 = 0.0; + SvNumFormatType nFmt1, nFmt2; + nFmt1 = nFmt2 = SvNumFormatType::UNDEFINED; + bool bDuration = false; + SvNumFormatType nFmtCurrencyType = nCurFmtType; + sal_uLong nFmtCurrencyIndex = nCurFmtIndex; + SvNumFormatType nFmtPercentType = nCurFmtType; + if ( GetStackType() == svMatrix ) + pMat2 = GetMatrix(); + else + { + fVal2 = GetDouble(); + switch ( nCurFmtType ) + { + case SvNumFormatType::DATE : + case SvNumFormatType::TIME : + case SvNumFormatType::DATETIME : + case SvNumFormatType::DURATION : + nFmt2 = nCurFmtType; + bDuration = true; + break; + case SvNumFormatType::CURRENCY : + nFmtCurrencyType = nCurFmtType; + nFmtCurrencyIndex = nCurFmtIndex; + break; + case SvNumFormatType::PERCENT : + nFmtPercentType = SvNumFormatType::PERCENT; + break; + default: break; + } + } + if ( GetStackType() == svMatrix ) + pMat1 = GetMatrix(); + else + { + fVal1 = GetDouble(); + switch ( nCurFmtType ) + { + case SvNumFormatType::DATE : + case SvNumFormatType::TIME : + case SvNumFormatType::DATETIME : + case SvNumFormatType::DURATION : + nFmt1 = nCurFmtType; + bDuration = true; + break; + case SvNumFormatType::CURRENCY : + nFmtCurrencyType = nCurFmtType; + nFmtCurrencyIndex = nCurFmtIndex; + break; + case SvNumFormatType::PERCENT : + nFmtPercentType = SvNumFormatType::PERCENT; + break; + default: break; + } + } + if (pMat1 && pMat2) + { + ScMatrixRef pResMat; + if ( _bSub ) + { + pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this, MatrixSub); + } + else + { + pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this, MatrixAdd); + } + + if (!pResMat) + PushNoValue(); + else + PushMatrix(pResMat); + } + else if (pMat1 || pMat2) + { + double fVal; + bool bFlag; + ScMatrixRef pMat = pMat1; + if (!pMat) + { + fVal = fVal1; + pMat = pMat2; + bFlag = true; // double - Matrix + } + else + { + fVal = fVal2; + bFlag = false; // Matrix - double + } + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + ScMatrixRef pResMat = GetNewMat(nC, nR, true); + if (pResMat) + { + if (_bSub) + { + pMat->SubOp( bFlag, fVal, *pResMat); + } + else + { + pMat->AddOp( fVal, *pResMat); + } + PushMatrix(pResMat); + } + else + PushIllegalArgument(); + } + else + { + // Determine nFuncFmtType type before PushDouble(). + if ( nFmtCurrencyType == SvNumFormatType::CURRENCY ) + { + nFuncFmtType = nFmtCurrencyType; + nFuncFmtIndex = nFmtCurrencyIndex; + } + else + { + lcl_GetDiffDateTimeFmtType( nFuncFmtType, nFmt1, nFmt2 ); + if (nFmtPercentType == SvNumFormatType::PERCENT && nFuncFmtType == SvNumFormatType::NUMBER) + nFuncFmtType = SvNumFormatType::PERCENT; + } + if ((nFuncFmtType == SvNumFormatType::DURATION || bDuration) + && ((_bSub && std::fabs(fVal1 - fVal2) <= SAL_MAX_INT32) + || (!_bSub && std::fabs(fVal1 + fVal2) <= SAL_MAX_INT32))) + { + // Limit to microseconds resolution on date inflicted or duration + // values of 24 hours or more. + const sal_uInt64 nEpsilon = ((std::fabs(fVal1) >= 1.0 || std::fabs(fVal2) >= 1.0) ? + ::tools::Duration::kAccuracyEpsilonNanosecondsMicroseconds : + ::tools::Duration::kAccuracyEpsilonNanoseconds); + if (_bSub) + PushDouble( ::tools::Duration( fVal1 - fVal2, nEpsilon).GetInDays()); + else + PushDouble( ::tools::Duration( fVal1 + fVal2, nEpsilon).GetInDays()); + } + else + { + if (_bSub) + PushDouble( ::rtl::math::approxSub( fVal1, fVal2 ) ); + else + PushDouble( ::rtl::math::approxAdd( fVal1, fVal2 ) ); + } + } +} + +void ScInterpreter::ScAmpersand() +{ + ScMatrixRef pMat1 = nullptr; + ScMatrixRef pMat2 = nullptr; + OUString sStr1, sStr2; + if ( GetStackType() == svMatrix ) + pMat2 = GetMatrix(); + else + sStr2 = GetString().getString(); + if ( GetStackType() == svMatrix ) + pMat1 = GetMatrix(); + else + sStr1 = GetString().getString(); + if (pMat1 && pMat2) + { + ScMatrixRef pResMat = MatConcat(pMat1, pMat2); + if (!pResMat) + PushNoValue(); + else + PushMatrix(pResMat); + } + else if (pMat1 || pMat2) + { + OUString sStr; + bool bFlag; + ScMatrixRef pMat = pMat1; + if (!pMat) + { + sStr = sStr1; + pMat = pMat2; + bFlag = true; // double - Matrix + } + else + { + sStr = sStr2; + bFlag = false; // Matrix - double + } + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + ScMatrixRef pResMat = GetNewMat(nC, nR, /*bEmpty*/true); + if (pResMat) + { + if (nGlobalError != FormulaError::NONE) + { + for (SCSIZE i = 0; i < nC; ++i) + for (SCSIZE j = 0; j < nR; ++j) + pResMat->PutError( nGlobalError, i, j); + } + else if (bFlag) + { + for (SCSIZE i = 0; i < nC; ++i) + for (SCSIZE j = 0; j < nR; ++j) + { + FormulaError nErr = pMat->GetErrorIfNotString( i, j); + if (nErr != FormulaError::NONE) + pResMat->PutError( nErr, i, j); + else + { + OUString aTmp = sStr + pMat->GetString(*pFormatter, i, j).getString(); + pResMat->PutString(mrStrPool.intern(aTmp), i, j); + } + } + } + else + { + for (SCSIZE i = 0; i < nC; ++i) + for (SCSIZE j = 0; j < nR; ++j) + { + FormulaError nErr = pMat->GetErrorIfNotString( i, j); + if (nErr != FormulaError::NONE) + pResMat->PutError( nErr, i, j); + else + { + OUString aTmp = pMat->GetString(*pFormatter, i, j).getString() + sStr; + pResMat->PutString(mrStrPool.intern(aTmp), i, j); + } + } + } + PushMatrix(pResMat); + } + else + PushIllegalArgument(); + } + else + { + if ( CheckStringResultLen( sStr1, sStr2.getLength() ) ) + sStr1 += sStr2; + PushString(sStr1); + } +} + +void ScInterpreter::ScSub() +{ + CalculateAddSub(true); +} + +void ScInterpreter::ScMul() +{ + ScMatrixRef pMat1 = nullptr; + ScMatrixRef pMat2 = nullptr; + double fVal1 = 0.0, fVal2 = 0.0; + SvNumFormatType nFmtCurrencyType = nCurFmtType; + sal_uLong nFmtCurrencyIndex = nCurFmtIndex; + if ( GetStackType() == svMatrix ) + pMat2 = GetMatrix(); + else + { + fVal2 = GetDouble(); + switch ( nCurFmtType ) + { + case SvNumFormatType::CURRENCY : + nFmtCurrencyType = nCurFmtType; + nFmtCurrencyIndex = nCurFmtIndex; + break; + default: break; + } + } + if ( GetStackType() == svMatrix ) + pMat1 = GetMatrix(); + else + { + fVal1 = GetDouble(); + switch ( nCurFmtType ) + { + case SvNumFormatType::CURRENCY : + nFmtCurrencyType = nCurFmtType; + nFmtCurrencyIndex = nCurFmtIndex; + break; + default: break; + } + } + if (pMat1 && pMat2) + { + ScMatrixRef pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this, MatrixMul); + if (!pResMat) + PushNoValue(); + else + PushMatrix(pResMat); + } + else if (pMat1 || pMat2) + { + double fVal; + ScMatrixRef pMat = pMat1; + if (!pMat) + { + fVal = fVal1; + pMat = pMat2; + } + else + fVal = fVal2; + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + ScMatrixRef pResMat = GetNewMat(nC, nR, /*bEmpty*/true); + if (pResMat) + { + pMat->MulOp( fVal, *pResMat); + PushMatrix(pResMat); + } + else + PushIllegalArgument(); + } + else + { + // Determine nFuncFmtType type before PushDouble(). + if ( nFmtCurrencyType == SvNumFormatType::CURRENCY ) + { + nFuncFmtType = nFmtCurrencyType; + nFuncFmtIndex = nFmtCurrencyIndex; + } + PushDouble(fVal1 * fVal2); + } +} + +void ScInterpreter::ScDiv() +{ + ScMatrixRef pMat1 = nullptr; + ScMatrixRef pMat2 = nullptr; + double fVal1 = 0.0, fVal2 = 0.0; + SvNumFormatType nFmtCurrencyType = nCurFmtType; + sal_uLong nFmtCurrencyIndex = nCurFmtIndex; + SvNumFormatType nFmtCurrencyType2 = SvNumFormatType::UNDEFINED; + if ( GetStackType() == svMatrix ) + pMat2 = GetMatrix(); + else + { + fVal2 = GetDouble(); + // do not take over currency, 123kg/456USD is not USD + nFmtCurrencyType2 = nCurFmtType; + } + if ( GetStackType() == svMatrix ) + pMat1 = GetMatrix(); + else + { + fVal1 = GetDouble(); + switch ( nCurFmtType ) + { + case SvNumFormatType::CURRENCY : + nFmtCurrencyType = nCurFmtType; + nFmtCurrencyIndex = nCurFmtIndex; + break; + default: break; + } + } + if (pMat1 && pMat2) + { + ScMatrixRef pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this, MatrixDiv); + if (!pResMat) + PushNoValue(); + else + PushMatrix(pResMat); + } + else if (pMat1 || pMat2) + { + double fVal; + bool bFlag; + ScMatrixRef pMat = pMat1; + if (!pMat) + { + fVal = fVal1; + pMat = pMat2; + bFlag = true; // double - Matrix + } + else + { + fVal = fVal2; + bFlag = false; // Matrix - double + } + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + ScMatrixRef pResMat = GetNewMat(nC, nR, /*bEmpty*/true); + if (pResMat) + { + pMat->DivOp( bFlag, fVal, *pResMat); + PushMatrix(pResMat); + } + else + PushIllegalArgument(); + } + else + { + // Determine nFuncFmtType type before PushDouble(). + if ( nFmtCurrencyType == SvNumFormatType::CURRENCY && + nFmtCurrencyType2 != SvNumFormatType::CURRENCY) + { // even USD/USD is not USD + nFuncFmtType = nFmtCurrencyType; + nFuncFmtIndex = nFmtCurrencyIndex; + } + PushDouble( div( fVal1, fVal2) ); + } +} + +void ScInterpreter::ScPower() +{ + if ( MustHaveParamCount( GetByte(), 2 ) ) + ScPow(); +} + +void ScInterpreter::ScPow() +{ + ScMatrixRef pMat1 = nullptr; + ScMatrixRef pMat2 = nullptr; + double fVal1 = 0.0, fVal2 = 0.0; + if ( GetStackType() == svMatrix ) + pMat2 = GetMatrix(); + else + fVal2 = GetDouble(); + if ( GetStackType() == svMatrix ) + pMat1 = GetMatrix(); + else + fVal1 = GetDouble(); + if (pMat1 && pMat2) + { + ScMatrixRef pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this, MatrixPow); + if (!pResMat) + PushNoValue(); + else + PushMatrix(pResMat); + } + else if (pMat1 || pMat2) + { + double fVal; + bool bFlag; + ScMatrixRef pMat = pMat1; + if (!pMat) + { + fVal = fVal1; + pMat = pMat2; + bFlag = true; // double - Matrix + } + else + { + fVal = fVal2; + bFlag = false; // Matrix - double + } + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + ScMatrixRef pResMat = GetNewMat(nC, nR, /*bEmpty*/true); + if (pResMat) + { + pMat->PowOp( bFlag, fVal, *pResMat); + PushMatrix(pResMat); + } + else + PushIllegalArgument(); + } + else + { + PushDouble( sc::power( fVal1, fVal2)); + } +} + +void ScInterpreter::ScSumProduct() +{ + short nParamCount = GetByte(); + if ( !MustHaveParamCountMin( nParamCount, 1) ) + return; + + // XXX NOTE: Excel returns #VALUE! for reference list and 0 (why?) for + // array of references. We calculate the proper individual arrays if sizes + // match. + + size_t nInRefList = 0; + ScMatrixRef pMatLast; + ScMatrixRef pMat; + + pMatLast = GetMatrix( --nParamCount, nInRefList); + if (!pMatLast) + { + PushIllegalParameter(); + return; + } + + SCSIZE nC, nCLast, nR, nRLast; + pMatLast->GetDimensions(nCLast, nRLast); + std::vector<double> aResArray; + pMatLast->GetDoubleArray(aResArray); + + while (nParamCount--) + { + pMat = GetMatrix( nParamCount, nInRefList); + if (!pMat) + { + PushIllegalParameter(); + return; + } + pMat->GetDimensions(nC, nR); + if (nC != nCLast || nR != nRLast) + { + PushNoValue(); + return; + } + + pMat->MergeDoubleArrayMultiply(aResArray); + } + + KahanSum fSum = 0.0; + for( double fPosArray : aResArray ) + { + FormulaError nErr = GetDoubleErrorValue(fPosArray); + if (nErr == FormulaError::NONE) + fSum += fPosArray; + else if (nErr != FormulaError::ElementNaN) + { + // Propagate the first error encountered, ignore "this is not a number" elements. + PushError(nErr); + return; + } + } + + PushDouble(fSum.get()); +} + +void ScInterpreter::ScSumX2MY2() +{ + CalculateSumX2MY2SumX2DY2(false); +} +void ScInterpreter::CalculateSumX2MY2SumX2DY2(bool _bSumX2DY2) +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + ScMatrixRef pMat1 = nullptr; + ScMatrixRef pMat2 = nullptr; + SCSIZE i, j; + pMat2 = GetMatrix(); + pMat1 = GetMatrix(); + if (!pMat2 || !pMat1) + { + PushIllegalParameter(); + return; + } + SCSIZE nC1, nC2; + SCSIZE nR1, nR2; + pMat2->GetDimensions(nC2, nR2); + pMat1->GetDimensions(nC1, nR1); + if (nC1 != nC2 || nR1 != nR2) + { + PushNoValue(); + return; + } + double fVal; + KahanSum fSum = 0.0; + for (i = 0; i < nC1; i++) + for (j = 0; j < nR1; j++) + if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j)) + { + fVal = pMat1->GetDouble(i,j); + fSum += fVal * fVal; + fVal = pMat2->GetDouble(i,j); + if ( _bSumX2DY2 ) + fSum += fVal * fVal; + else + fSum -= fVal * fVal; + } + PushDouble(fSum.get()); +} + +void ScInterpreter::ScSumX2DY2() +{ + CalculateSumX2MY2SumX2DY2(true); +} + +void ScInterpreter::ScSumXMY2() +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + ScMatrixRef pMat2 = GetMatrix(); + ScMatrixRef pMat1 = GetMatrix(); + if (!pMat2 || !pMat1) + { + PushIllegalParameter(); + return; + } + SCSIZE nC1, nC2; + SCSIZE nR1, nR2; + pMat2->GetDimensions(nC2, nR2); + pMat1->GetDimensions(nC1, nR1); + if (nC1 != nC2 || nR1 != nR2) + { + PushNoValue(); + return; + } // if (nC1 != nC2 || nR1 != nR2) + ScMatrixRef pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this, MatrixSub); + if (!pResMat) + { + PushNoValue(); + } + else + { + PushDouble(pResMat->SumSquare(false).maAccumulator.get()); + } +} + +void ScInterpreter::ScFrequency() +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + vector<double> aBinArray; + vector<tools::Long> aBinIndexOrder; + + GetSortArray( 1, aBinArray, &aBinIndexOrder, false, false ); + SCSIZE nBinSize = aBinArray.size(); + if (nGlobalError != FormulaError::NONE) + { + PushNoValue(); + return; + } + + vector<double> aDataArray; + GetSortArray( 1, aDataArray, nullptr, false, false ); + SCSIZE nDataSize = aDataArray.size(); + + if (aDataArray.empty() || nGlobalError != FormulaError::NONE) + { + PushNoValue(); + return; + } + ScMatrixRef pResMat = GetNewMat(1, nBinSize+1, /*bEmpty*/true); + if (!pResMat) + { + PushIllegalArgument(); + return; + } + + if (nBinSize != aBinIndexOrder.size()) + { + PushIllegalArgument(); + return; + } + + SCSIZE j; + SCSIZE i = 0; + for (j = 0; j < nBinSize; ++j) + { + SCSIZE nCount = 0; + while (i < nDataSize && aDataArray[i] <= aBinArray[j]) + { + ++nCount; + ++i; + } + pResMat->PutDouble(static_cast<double>(nCount), aBinIndexOrder[j]); + } + pResMat->PutDouble(static_cast<double>(nDataSize-i), j); + PushMatrix(pResMat); +} + +namespace { + +// Helper methods for LINEST/LOGEST and TREND/GROWTH +// All matrices must already exist and have the needed size, no control tests +// done. Those methods, which names start with lcl_T, are adapted to case 3, +// where Y (=observed values) is given as row. +// Remember, ScMatrix matrices are zero based, index access (column,row). + +// <A;B> over all elements; uses the matrices as vectors of length M +double lcl_GetSumProduct(const ScMatrixRef& pMatA, const ScMatrixRef& pMatB, SCSIZE nM) +{ + KahanSum fSum = 0.0; + for (SCSIZE i=0; i<nM; i++) + fSum += pMatA->GetDouble(i) * pMatB->GetDouble(i); + return fSum.get(); +} + +// Special version for use within QR decomposition. +// Euclidean norm of column index C starting in row index R; +// matrix A has count N rows. +double lcl_GetColumnEuclideanNorm(const ScMatrixRef& pMatA, SCSIZE nC, SCSIZE nR, SCSIZE nN) +{ + KahanSum fNorm = 0.0; + for (SCSIZE row=nR; row<nN; row++) + fNorm += (pMatA->GetDouble(nC,row)) * (pMatA->GetDouble(nC,row)); + return sqrt(fNorm.get()); +} + +// Euclidean norm of row index R starting in column index C; +// matrix A has count N columns. +double lcl_TGetColumnEuclideanNorm(const ScMatrixRef& pMatA, SCSIZE nR, SCSIZE nC, SCSIZE nN) +{ + KahanSum fNorm = 0.0; + for (SCSIZE col=nC; col<nN; col++) + fNorm += (pMatA->GetDouble(col,nR)) * (pMatA->GetDouble(col,nR)); + return sqrt(fNorm.get()); +} + +// Special version for use within QR decomposition. +// Maximum norm of column index C starting in row index R; +// matrix A has count N rows. +double lcl_GetColumnMaximumNorm(const ScMatrixRef& pMatA, SCSIZE nC, SCSIZE nR, SCSIZE nN) +{ + double fNorm = 0.0; + for (SCSIZE row=nR; row<nN; row++) + { + double fVal = fabs(pMatA->GetDouble(nC,row)); + if (fNorm < fVal) + fNorm = fVal; + } + return fNorm; +} + +// Maximum norm of row index R starting in col index C; +// matrix A has count N columns. +double lcl_TGetColumnMaximumNorm(const ScMatrixRef& pMatA, SCSIZE nR, SCSIZE nC, SCSIZE nN) +{ + double fNorm = 0.0; + for (SCSIZE col=nC; col<nN; col++) + { + double fVal = fabs(pMatA->GetDouble(col,nR)); + if (fNorm < fVal) + fNorm = fVal; + } + return fNorm; +} + +// Special version for use within QR decomposition. +// <A(Ca);B(Cb)> starting in row index R; +// Ca and Cb are indices of columns, matrices A and B have count N rows. +double lcl_GetColumnSumProduct(const ScMatrixRef& pMatA, SCSIZE nCa, + const ScMatrixRef& pMatB, SCSIZE nCb, SCSIZE nR, SCSIZE nN) +{ + KahanSum fResult = 0.0; + for (SCSIZE row=nR; row<nN; row++) + fResult += pMatA->GetDouble(nCa,row) * pMatB->GetDouble(nCb,row); + return fResult.get(); +} + +// <A(Ra);B(Rb)> starting in column index C; +// Ra and Rb are indices of rows, matrices A and B have count N columns. +double lcl_TGetColumnSumProduct(const ScMatrixRef& pMatA, SCSIZE nRa, + const ScMatrixRef& pMatB, SCSIZE nRb, SCSIZE nC, SCSIZE nN) +{ + KahanSum fResult = 0.0; + for (SCSIZE col=nC; col<nN; col++) + fResult += pMatA->GetDouble(col,nRa) * pMatB->GetDouble(col,nRb); + return fResult.get(); +} + +// no mathematical signum, but used to switch between adding and subtracting +double lcl_GetSign(double fValue) +{ + return (fValue >= 0.0 ? 1.0 : -1.0 ); +} + +/* Calculates a QR decomposition with Householder reflection. + * For each NxK matrix A exists a decomposition A=Q*R with an orthogonal + * NxN matrix Q and a NxK matrix R. + * Q=H1*H2*...*Hk with Householder matrices H. Such a householder matrix can + * be build from a vector u by H=I-(2/u'u)*(u u'). This vectors u are returned + * in the columns of matrix A, overwriting the old content. + * The matrix R has a quadric upper part KxK with values in the upper right + * triangle and zeros in all other elements. Here the diagonal elements of R + * are stored in the vector R and the other upper right elements in the upper + * right of the matrix A. + * The function returns false, if calculation breaks. But because of round-off + * errors singularity is often not detected. + */ +bool lcl_CalculateQRdecomposition(const ScMatrixRef& pMatA, + ::std::vector< double>& pVecR, SCSIZE nK, SCSIZE nN) +{ + // ScMatrix matrices are zero based, index access (column,row) + for (SCSIZE col = 0; col <nK; col++) + { + // calculate vector u of the householder transformation + const double fScale = lcl_GetColumnMaximumNorm(pMatA, col, col, nN); + if (fScale == 0.0) + { + // A is singular + return false; + } + for (SCSIZE row = col; row <nN; row++) + pMatA->PutDouble( pMatA->GetDouble(col,row)/fScale, col, row); + + const double fEuclid = lcl_GetColumnEuclideanNorm(pMatA, col, col, nN); + const double fFactor = 1.0/fEuclid/(fEuclid + fabs(pMatA->GetDouble(col,col))); + const double fSignum = lcl_GetSign(pMatA->GetDouble(col,col)); + pMatA->PutDouble( pMatA->GetDouble(col,col) + fSignum*fEuclid, col,col); + pVecR[col] = -fSignum * fScale * fEuclid; + + // apply Householder transformation to A + for (SCSIZE c=col+1; c<nK; c++) + { + const double fSum =lcl_GetColumnSumProduct(pMatA, col, pMatA, c, col, nN); + for (SCSIZE row = col; row <nN; row++) + pMatA->PutDouble( pMatA->GetDouble(c,row) - fSum * fFactor * pMatA->GetDouble(col,row), c, row); + } + } + return true; +} + +// same with transposed matrix A, N is count of columns, K count of rows +bool lcl_TCalculateQRdecomposition(const ScMatrixRef& pMatA, + ::std::vector< double>& pVecR, SCSIZE nK, SCSIZE nN) +{ + double fSum ; + // ScMatrix matrices are zero based, index access (column,row) + for (SCSIZE row = 0; row <nK; row++) + { + // calculate vector u of the householder transformation + const double fScale = lcl_TGetColumnMaximumNorm(pMatA, row, row, nN); + if (fScale == 0.0) + { + // A is singular + return false; + } + for (SCSIZE col = row; col <nN; col++) + pMatA->PutDouble( pMatA->GetDouble(col,row)/fScale, col, row); + + const double fEuclid = lcl_TGetColumnEuclideanNorm(pMatA, row, row, nN); + const double fFactor = 1.0/fEuclid/(fEuclid + fabs(pMatA->GetDouble(row,row))); + const double fSignum = lcl_GetSign(pMatA->GetDouble(row,row)); + pMatA->PutDouble( pMatA->GetDouble(row,row) + fSignum*fEuclid, row,row); + pVecR[row] = -fSignum * fScale * fEuclid; + + // apply Householder transformation to A + for (SCSIZE r=row+1; r<nK; r++) + { + fSum =lcl_TGetColumnSumProduct(pMatA, row, pMatA, r, row, nN); + for (SCSIZE col = row; col <nN; col++) + pMatA->PutDouble( + pMatA->GetDouble(col,r) - fSum * fFactor * pMatA->GetDouble(col,row), col, r); + } + } + return true; +} + +/* Applies a Householder transformation to a column vector Y with is given as + * Nx1 Matrix. The vector u, from which the Householder transformation is built, + * is the column part in matrix A, with column index C, starting with row + * index C. A is the result of the QR decomposition as obtained from + * lcl_CalculateQRdecomposition. + */ +void lcl_ApplyHouseholderTransformation(const ScMatrixRef& pMatA, SCSIZE nC, + const ScMatrixRef& pMatY, SCSIZE nN) +{ + // ScMatrix matrices are zero based, index access (column,row) + double fDenominator = lcl_GetColumnSumProduct(pMatA, nC, pMatA, nC, nC, nN); + double fNumerator = lcl_GetColumnSumProduct(pMatA, nC, pMatY, 0, nC, nN); + double fFactor = 2.0 * (fNumerator/fDenominator); + for (SCSIZE row = nC; row < nN; row++) + pMatY->PutDouble( + pMatY->GetDouble(row) - fFactor * pMatA->GetDouble(nC,row), row); +} + +// Same with transposed matrices A and Y. +void lcl_TApplyHouseholderTransformation(const ScMatrixRef& pMatA, SCSIZE nR, + const ScMatrixRef& pMatY, SCSIZE nN) +{ + // ScMatrix matrices are zero based, index access (column,row) + double fDenominator = lcl_TGetColumnSumProduct(pMatA, nR, pMatA, nR, nR, nN); + double fNumerator = lcl_TGetColumnSumProduct(pMatA, nR, pMatY, 0, nR, nN); + double fFactor = 2.0 * (fNumerator/fDenominator); + for (SCSIZE col = nR; col < nN; col++) + pMatY->PutDouble( + pMatY->GetDouble(col) - fFactor * pMatA->GetDouble(col,nR), col); +} + +/* Solve for X in R*X=S using back substitution. The solution X overwrites S. + * Uses R from the result of the QR decomposition of a NxK matrix A. + * S is a column vector given as matrix, with at least elements on index + * 0 to K-1; elements on index>=K are ignored. Vector R must not have zero + * elements, no check is done. + */ +void lcl_SolveWithUpperRightTriangle(const ScMatrixRef& pMatA, + ::std::vector< double>& pVecR, const ScMatrixRef& pMatS, + SCSIZE nK, bool bIsTransposed) +{ + // ScMatrix matrices are zero based, index access (column,row) + SCSIZE row; + // SCSIZE is never negative, therefore test with rowp1=row+1 + for (SCSIZE rowp1 = nK; rowp1>0; rowp1--) + { + row = rowp1-1; + KahanSum fSum = pMatS->GetDouble(row); + for (SCSIZE col = rowp1; col<nK ; col++) + if (bIsTransposed) + fSum -= pMatA->GetDouble(row,col) * pMatS->GetDouble(col); + else + fSum -= pMatA->GetDouble(col,row) * pMatS->GetDouble(col); + pMatS->PutDouble( fSum.get() / pVecR[row] , row); + } +} + +/* Solve for X in R' * X= T using forward substitution. The solution X + * overwrites T. Uses R from the result of the QR decomposition of a NxK + * matrix A. T is a column vectors given as matrix, with at least elements on + * index 0 to K-1; elements on index>=K are ignored. Vector R must not have + * zero elements, no check is done. + */ +void lcl_SolveWithLowerLeftTriangle(const ScMatrixRef& pMatA, + ::std::vector< double>& pVecR, const ScMatrixRef& pMatT, + SCSIZE nK, bool bIsTransposed) +{ + // ScMatrix matrices are zero based, index access (column,row) + for (SCSIZE row = 0; row < nK; row++) + { + KahanSum fSum = pMatT -> GetDouble(row); + for (SCSIZE col=0; col < row; col++) + { + if (bIsTransposed) + fSum -= pMatA->GetDouble(col,row) * pMatT->GetDouble(col); + else + fSum -= pMatA->GetDouble(row,col) * pMatT->GetDouble(col); + } + pMatT->PutDouble( fSum.get() / pVecR[row] , row); + } +} + +/* Calculates Z = R * B + * R is given in matrix A and vector VecR as obtained from the QR + * decomposition in lcl_CalculateQRdecomposition. B and Z are column vectors + * given as matrix with at least index 0 to K-1; elements on index>=K are + * not used. + */ +void lcl_ApplyUpperRightTriangle(const ScMatrixRef& pMatA, + ::std::vector< double>& pVecR, const ScMatrixRef& pMatB, + const ScMatrixRef& pMatZ, SCSIZE nK, bool bIsTransposed) +{ + // ScMatrix matrices are zero based, index access (column,row) + for (SCSIZE row = 0; row < nK; row++) + { + KahanSum fSum = pVecR[row] * pMatB->GetDouble(row); + for (SCSIZE col = row+1; col < nK; col++) + if (bIsTransposed) + fSum += pMatA->GetDouble(row,col) * pMatB->GetDouble(col); + else + fSum += pMatA->GetDouble(col,row) * pMatB->GetDouble(col); + pMatZ->PutDouble( fSum.get(), row); + } +} + +double lcl_GetMeanOverAll(const ScMatrixRef& pMat, SCSIZE nN) +{ + KahanSum fSum = 0.0; + for (SCSIZE i=0 ; i<nN; i++) + fSum += pMat->GetDouble(i); + return fSum.get()/static_cast<double>(nN); +} + +// Calculates means of the columns of matrix X. X is a RxC matrix; +// ResMat is a 1xC matrix (=row). +void lcl_CalculateColumnMeans(const ScMatrixRef& pX, const ScMatrixRef& pResMat, + SCSIZE nC, SCSIZE nR) +{ + for (SCSIZE i=0; i < nC; i++) + { + KahanSum fSum =0.0; + for (SCSIZE k=0; k < nR; k++) + fSum += pX->GetDouble(i,k); // GetDouble(Column,Row) + pResMat ->PutDouble( fSum.get()/static_cast<double>(nR),i); + } +} + +// Calculates means of the rows of matrix X. X is a RxC matrix; +// ResMat is a Rx1 matrix (=column). +void lcl_CalculateRowMeans(const ScMatrixRef& pX, const ScMatrixRef& pResMat, + SCSIZE nC, SCSIZE nR) +{ + for (SCSIZE k=0; k < nR; k++) + { + KahanSum fSum = 0.0; + for (SCSIZE i=0; i < nC; i++) + fSum += pX->GetDouble(i,k); // GetDouble(Column,Row) + pResMat ->PutDouble( fSum.get()/static_cast<double>(nC),k); + } +} + +void lcl_CalculateColumnsDelta(const ScMatrixRef& pMat, const ScMatrixRef& pColumnMeans, + SCSIZE nC, SCSIZE nR) +{ + for (SCSIZE i = 0; i < nC; i++) + for (SCSIZE k = 0; k < nR; k++) + pMat->PutDouble( ::rtl::math::approxSub + (pMat->GetDouble(i,k) , pColumnMeans->GetDouble(i) ) , i, k); +} + +void lcl_CalculateRowsDelta(const ScMatrixRef& pMat, const ScMatrixRef& pRowMeans, + SCSIZE nC, SCSIZE nR) +{ + for (SCSIZE k = 0; k < nR; k++) + for (SCSIZE i = 0; i < nC; i++) + pMat->PutDouble( ::rtl::math::approxSub + ( pMat->GetDouble(i,k) , pRowMeans->GetDouble(k) ) , i, k); +} + +// Case1 = simple regression +// MatX = X - MeanX, MatY = Y - MeanY, y - haty = (y - MeanY) - (haty - MeanY) +// = (y-MeanY)-((slope*x+a)-(slope*MeanX+a)) = (y-MeanY)-slope*(x-MeanX) +double lcl_GetSSresid(const ScMatrixRef& pMatX, const ScMatrixRef& pMatY, double fSlope, + SCSIZE nN) +{ + KahanSum fSum = 0.0; + for (SCSIZE i=0; i<nN; i++) + { + const double fTemp = pMatY->GetDouble(i) - fSlope * pMatX->GetDouble(i); + fSum += fTemp * fTemp; + } + return fSum.get(); +} + +} + +// Fill default values in matrix X, transform Y to log(Y) in case LOGEST|GROWTH, +// determine sizes of matrices X and Y, determine kind of regression, clone +// Y in case LOGEST|GROWTH, if constant. +bool ScInterpreter::CheckMatrix(bool _bLOG, sal_uInt8& nCase, SCSIZE& nCX, + SCSIZE& nCY, SCSIZE& nRX, SCSIZE& nRY, SCSIZE& M, + SCSIZE& N, ScMatrixRef& pMatX, ScMatrixRef& pMatY) +{ + + nCX = 0; + nCY = 0; + nRX = 0; + nRY = 0; + M = 0; + N = 0; + pMatY->GetDimensions(nCY, nRY); + const SCSIZE nCountY = nCY * nRY; + for ( SCSIZE i = 0; i < nCountY; i++ ) + { + if (!pMatY->IsValue(i)) + { + PushIllegalArgument(); + return false; + } + } + + if ( _bLOG ) + { + ScMatrixRef pNewY = pMatY->CloneIfConst(); + for (SCSIZE nElem = 0; nElem < nCountY; nElem++) + { + const double fVal = pNewY->GetDouble(nElem); + if (fVal <= 0.0) + { + PushIllegalArgument(); + return false; + } + else + pNewY->PutDouble(log(fVal), nElem); + } + pMatY = pNewY; + } + + if (pMatX) + { + pMatX->GetDimensions(nCX, nRX); + const SCSIZE nCountX = nCX * nRX; + for ( SCSIZE i = 0; i < nCountX; i++ ) + if (!pMatX->IsValue(i)) + { + PushIllegalArgument(); + return false; + } + if (nCX == nCY && nRX == nRY) + { + nCase = 1; // simple regression + M = 1; + N = nCountY; + } + else if (nCY != 1 && nRY != 1) + { + PushIllegalArgument(); + return false; + } + else if (nCY == 1) + { + if (nRX != nRY) + { + PushIllegalArgument(); + return false; + } + else + { + nCase = 2; // Y is column + N = nRY; + M = nCX; + } + } + else if (nCX != nCY) + { + PushIllegalArgument(); + return false; + } + else + { + nCase = 3; // Y is row + N = nCY; + M = nRX; + } + } + else + { + pMatX = GetNewMat(nCY, nRY, /*bEmpty*/true); + nCX = nCY; + nRX = nRY; + if (!pMatX) + { + PushIllegalArgument(); + return false; + } + for ( SCSIZE i = 1; i <= nCountY; i++ ) + pMatX->PutDouble(static_cast<double>(i), i-1); + nCase = 1; + N = nCountY; + M = 1; + } + return true; +} + +// LINEST +void ScInterpreter::ScLinest() +{ + CalculateRGPRKP(false); +} + +// LOGEST +void ScInterpreter::ScLogest() +{ + CalculateRGPRKP(true); +} + +void ScInterpreter::CalculateRGPRKP(bool _bRKP) +{ + sal_uInt8 nParamCount = GetByte(); + if (!MustHaveParamCount( nParamCount, 1, 4 )) + return; + bool bConstant, bStats; + + // optional forth parameter + if (nParamCount == 4) + bStats = GetBool(); + else + bStats = false; + + // The third parameter may not be missing in ODF, if the forth parameter + // is present. But Excel allows it with default true, we too. + if (nParamCount >= 3) + { + if (IsMissing()) + { + Pop(); + bConstant = true; +// PushIllegalParameter(); if ODF behavior is desired +// return; + } + else + bConstant = GetBool(); + } + else + bConstant = true; + + ScMatrixRef pMatX; + if (nParamCount >= 2) + { + if (IsMissing()) + { //In ODF1.2 empty second parameter (which is two ;; ) is allowed + Pop(); + pMatX = nullptr; + } + else + { + pMatX = GetMatrix(); + } + } + else + pMatX = nullptr; + + ScMatrixRef pMatY = GetMatrix(); + if (!pMatY) + { + PushIllegalParameter(); + return; + } + + // 1 = simple; 2 = multiple with Y as column; 3 = multiple with Y as row + sal_uInt8 nCase; + + SCSIZE nCX, nCY; // number of columns + SCSIZE nRX, nRY; //number of rows + SCSIZE K = 0, N = 0; // K=number of variables X, N=number of data samples + if (!CheckMatrix(_bRKP,nCase,nCX,nCY,nRX,nRY,K,N,pMatX,pMatY)) + { + PushIllegalParameter(); + return; + } + + // Enough data samples? + if ((bConstant && (N<K+1)) || (!bConstant && (N<K)) || (N<1) || (K<1)) + { + PushIllegalParameter(); + return; + } + + ScMatrixRef pResMat; + if (bStats) + pResMat = GetNewMat(K+1,5, /*bEmpty*/true); + else + pResMat = GetNewMat(K+1,1, /*bEmpty*/true); + if (!pResMat) + { + PushError(FormulaError::CodeOverflow); + return; + } + // Fill unused cells in pResMat; order (column,row) + if (bStats) + { + for (SCSIZE i=2; i<K+1; i++) + { + pResMat->PutError( FormulaError::NotAvailable, i, 2); + pResMat->PutError( FormulaError::NotAvailable, i, 3); + pResMat->PutError( FormulaError::NotAvailable, i, 4); + } + } + + // Uses sum(x-MeanX)^2 and not [sum x^2]-N * MeanX^2 in case bConstant. + // Clone constant matrices, so that Mat = Mat - Mean is possible. + double fMeanY = 0.0; + if (bConstant) + { + ScMatrixRef pNewX = pMatX->CloneIfConst(); + ScMatrixRef pNewY = pMatY->CloneIfConst(); + if (!pNewX || !pNewY) + { + PushError(FormulaError::CodeOverflow); + return; + } + pMatX = pNewX; + pMatY = pNewY; + // DeltaY is possible here; DeltaX depends on nCase, so later + fMeanY = lcl_GetMeanOverAll(pMatY, N); + for (SCSIZE i=0; i<N; i++) + { + pMatY->PutDouble( ::rtl::math::approxSub(pMatY->GetDouble(i),fMeanY), i ); + } + } + + if (nCase==1) + { + // calculate simple regression + double fMeanX = 0.0; + if (bConstant) + { // Mat = Mat - Mean + fMeanX = lcl_GetMeanOverAll(pMatX, N); + for (SCSIZE i=0; i<N; i++) + { + pMatX->PutDouble( ::rtl::math::approxSub(pMatX->GetDouble(i),fMeanX), i ); + } + } + double fSumXY = lcl_GetSumProduct(pMatX,pMatY,N); + double fSumX2 = lcl_GetSumProduct(pMatX,pMatX,N); + if (fSumX2==0.0) + { + PushNoValue(); // all x-values are identical + return; + } + double fSlope = fSumXY / fSumX2; + double fIntercept = 0.0; + if (bConstant) + fIntercept = fMeanY - fSlope * fMeanX; + pResMat->PutDouble(_bRKP ? exp(fIntercept) : fIntercept, 1, 0); //order (column,row) + pResMat->PutDouble(_bRKP ? exp(fSlope) : fSlope, 0, 0); + + if (bStats) + { + double fSSreg = fSlope * fSlope * fSumX2; + pResMat->PutDouble(fSSreg, 0, 4); + + double fDegreesFreedom =static_cast<double>( bConstant ? N-2 : N-1 ); + pResMat->PutDouble(fDegreesFreedom, 1, 3); + + double fSSresid = lcl_GetSSresid(pMatX,pMatY,fSlope,N); + pResMat->PutDouble(fSSresid, 1, 4); + + if (fDegreesFreedom == 0.0 || fSSresid == 0.0 || fSSreg == 0.0) + { // exact fit; test SSreg too, because SSresid might be + // unequal zero due to round of errors + pResMat->PutDouble(0.0, 1, 4); // SSresid + pResMat->PutError( FormulaError::NotAvailable, 0, 3); // F + pResMat->PutDouble(0.0, 1, 2); // RMSE + pResMat->PutDouble(0.0, 0, 1); // SigmaSlope + if (bConstant) + pResMat->PutDouble(0.0, 1, 1); //SigmaIntercept + else + pResMat->PutError( FormulaError::NotAvailable, 1, 1); + pResMat->PutDouble(1.0, 0, 2); // R^2 + } + else + { + double fFstatistic = (fSSreg / static_cast<double>(K)) + / (fSSresid / fDegreesFreedom); + pResMat->PutDouble(fFstatistic, 0, 3); + + // standard error of estimate + double fRMSE = sqrt(fSSresid / fDegreesFreedom); + pResMat->PutDouble(fRMSE, 1, 2); + + double fSigmaSlope = fRMSE / sqrt(fSumX2); + pResMat->PutDouble(fSigmaSlope, 0, 1); + + if (bConstant) + { + double fSigmaIntercept = fRMSE + * sqrt(fMeanX*fMeanX/fSumX2 + 1.0/static_cast<double>(N)); + pResMat->PutDouble(fSigmaIntercept, 1, 1); + } + else + { + pResMat->PutError( FormulaError::NotAvailable, 1, 1); + } + + double fR2 = fSSreg / (fSSreg + fSSresid); + pResMat->PutDouble(fR2, 0, 2); + } + } + PushMatrix(pResMat); + } + else // calculate multiple regression; + { + // Uses a QR decomposition X = QR. The solution B = (X'X)^(-1) * X' * Y + // becomes B = R^(-1) * Q' * Y + if (nCase ==2) // Y is column + { + ::std::vector< double> aVecR(N); // for QR decomposition + // Enough memory for needed matrices? + ScMatrixRef pMeans = GetNewMat(K, 1, /*bEmpty*/true); // mean of each column + ScMatrixRef pMatZ; // for Q' * Y , inter alia + if (bStats) + pMatZ = pMatY->Clone(); // Y is used in statistic, keep it + else + pMatZ = pMatY; // Y can be overwritten + ScMatrixRef pSlopes = GetNewMat(1,K, /*bEmpty*/true); // from b1 to bK + if (!pMeans || !pMatZ || !pSlopes) + { + PushError(FormulaError::CodeOverflow); + return; + } + if (bConstant) + { + lcl_CalculateColumnMeans(pMatX, pMeans, K, N); + lcl_CalculateColumnsDelta(pMatX, pMeans, K, N); + } + if (!lcl_CalculateQRdecomposition(pMatX, aVecR, K, N)) + { + PushNoValue(); + return; + } + // Later on we will divide by elements of aVecR, so make sure + // that they aren't zero. + bool bIsSingular=false; + for (SCSIZE row=0; row < K && !bIsSingular; row++) + bIsSingular = aVecR[row] == 0.0; + if (bIsSingular) + { + PushNoValue(); + return; + } + // Z = Q' Y; + for (SCSIZE col = 0; col < K; col++) + { + lcl_ApplyHouseholderTransformation(pMatX, col, pMatZ, N); + } + // B = R^(-1) * Q' * Y <=> B = R^(-1) * Z <=> R * B = Z + // result Z should have zeros for index>=K; if not, ignore values + for (SCSIZE col = 0; col < K ; col++) + { + pSlopes->PutDouble( pMatZ->GetDouble(col), col); + } + lcl_SolveWithUpperRightTriangle(pMatX, aVecR, pSlopes, K, false); + double fIntercept = 0.0; + if (bConstant) + fIntercept = fMeanY - lcl_GetSumProduct(pMeans,pSlopes,K); + // Fill first line in result matrix + pResMat->PutDouble(_bRKP ? exp(fIntercept) : fIntercept, K, 0 ); + for (SCSIZE i = 0; i < K; i++) + pResMat->PutDouble(_bRKP ? exp(pSlopes->GetDouble(i)) + : pSlopes->GetDouble(i) , K-1-i, 0); + + if (bStats) + { + double fSSreg = 0.0; + double fSSresid = 0.0; + // re-use memory of Z; + pMatZ->FillDouble(0.0, 0, 0, 0, N-1); + // Z = R * Slopes + lcl_ApplyUpperRightTriangle(pMatX, aVecR, pSlopes, pMatZ, K, false); + // Z = Q * Z, that is Q * R * Slopes = X * Slopes + for (SCSIZE colp1 = K; colp1 > 0; colp1--) + { + lcl_ApplyHouseholderTransformation(pMatX, colp1-1, pMatZ,N); + } + fSSreg =lcl_GetSumProduct(pMatZ, pMatZ, N); + // re-use Y for residuals, Y = Y-Z + for (SCSIZE row = 0; row < N; row++) + pMatY->PutDouble(pMatY->GetDouble(row) - pMatZ->GetDouble(row), row); + fSSresid = lcl_GetSumProduct(pMatY, pMatY, N); + pResMat->PutDouble(fSSreg, 0, 4); + pResMat->PutDouble(fSSresid, 1, 4); + + double fDegreesFreedom =static_cast<double>( bConstant ? N-K-1 : N-K ); + pResMat->PutDouble(fDegreesFreedom, 1, 3); + + if (fDegreesFreedom == 0.0 || fSSresid == 0.0 || fSSreg == 0.0) + { // exact fit; incl. observed values Y are identical + pResMat->PutDouble(0.0, 1, 4); // SSresid + // F = (SSreg/K) / (SSresid/df) = #DIV/0! + pResMat->PutError( FormulaError::NotAvailable, 0, 3); // F + // RMSE = sqrt(SSresid / df) = sqrt(0 / df) = 0 + pResMat->PutDouble(0.0, 1, 2); // RMSE + // SigmaSlope[i] = RMSE * sqrt(matrix[i,i]) = 0 * sqrt(...) = 0 + for (SCSIZE i=0; i<K; i++) + pResMat->PutDouble(0.0, K-1-i, 1); + + // SigmaIntercept = RMSE * sqrt(...) = 0 + if (bConstant) + pResMat->PutDouble(0.0, K, 1); //SigmaIntercept + else + pResMat->PutError( FormulaError::NotAvailable, K, 1); + + // R^2 = SSreg / (SSreg + SSresid) = 1.0 + pResMat->PutDouble(1.0, 0, 2); // R^2 + } + else + { + double fFstatistic = (fSSreg / static_cast<double>(K)) + / (fSSresid / fDegreesFreedom); + pResMat->PutDouble(fFstatistic, 0, 3); + + // standard error of estimate = root mean SSE + double fRMSE = sqrt(fSSresid / fDegreesFreedom); + pResMat->PutDouble(fRMSE, 1, 2); + + // standard error of slopes + // = RMSE * sqrt(diagonal element of (R' R)^(-1) ) + // standard error of intercept + // = RMSE * sqrt( Xmean * (R' R)^(-1) * Xmean' + 1/N) + // (R' R)^(-1) = R^(-1) * (R')^(-1). Do not calculate it as + // a whole matrix, but iterate over unit vectors. + KahanSum aSigmaIntercept = 0.0; + double fPart; // for Xmean * single column of (R' R)^(-1) + for (SCSIZE col = 0; col < K; col++) + { + //re-use memory of MatZ + pMatZ->FillDouble(0.0,0,0,0,K-1); // Z = unit vector e + pMatZ->PutDouble(1.0, col); + //Solve R' * Z = e + lcl_SolveWithLowerLeftTriangle(pMatX, aVecR, pMatZ, K, false); + // Solve R * Znew = Zold + lcl_SolveWithUpperRightTriangle(pMatX, aVecR, pMatZ, K, false); + // now Z is column col in (R' R)^(-1) + double fSigmaSlope = fRMSE * sqrt(pMatZ->GetDouble(col)); + pResMat->PutDouble(fSigmaSlope, K-1-col, 1); + // (R' R) ^(-1) is symmetric + if (bConstant) + { + fPart = lcl_GetSumProduct(pMeans, pMatZ, K); + aSigmaIntercept += fPart * pMeans->GetDouble(col); + } + } + if (bConstant) + { + double fSigmaIntercept = fRMSE + * sqrt( (aSigmaIntercept + 1.0 / static_cast<double>(N) ).get() ); + pResMat->PutDouble(fSigmaIntercept, K, 1); + } + else + { + pResMat->PutError( FormulaError::NotAvailable, K, 1); + } + + double fR2 = fSSreg / (fSSreg + fSSresid); + pResMat->PutDouble(fR2, 0, 2); + } + } + PushMatrix(pResMat); + } + else // nCase == 3, Y is row, all matrices are transposed + { + ::std::vector< double> aVecR(N); // for QR decomposition + // Enough memory for needed matrices? + ScMatrixRef pMeans = GetNewMat(1, K, /*bEmpty*/true); // mean of each row + ScMatrixRef pMatZ; // for Q' * Y , inter alia + if (bStats) + pMatZ = pMatY->Clone(); // Y is used in statistic, keep it + else + pMatZ = pMatY; // Y can be overwritten + ScMatrixRef pSlopes = GetNewMat(K,1, /*bEmpty*/true); // from b1 to bK + if (!pMeans || !pMatZ || !pSlopes) + { + PushError(FormulaError::CodeOverflow); + return; + } + if (bConstant) + { + lcl_CalculateRowMeans(pMatX, pMeans, N, K); + lcl_CalculateRowsDelta(pMatX, pMeans, N, K); + } + + if (!lcl_TCalculateQRdecomposition(pMatX, aVecR, K, N)) + { + PushNoValue(); + return; + } + + // Later on we will divide by elements of aVecR, so make sure + // that they aren't zero. + bool bIsSingular=false; + for (SCSIZE row=0; row < K && !bIsSingular; row++) + bIsSingular = aVecR[row] == 0.0; + if (bIsSingular) + { + PushNoValue(); + return; + } + // Z = Q' Y + for (SCSIZE row = 0; row < K; row++) + { + lcl_TApplyHouseholderTransformation(pMatX, row, pMatZ, N); + } + // B = R^(-1) * Q' * Y <=> B = R^(-1) * Z <=> R * B = Z + // result Z should have zeros for index>=K; if not, ignore values + for (SCSIZE col = 0; col < K ; col++) + { + pSlopes->PutDouble( pMatZ->GetDouble(col), col); + } + lcl_SolveWithUpperRightTriangle(pMatX, aVecR, pSlopes, K, true); + double fIntercept = 0.0; + if (bConstant) + fIntercept = fMeanY - lcl_GetSumProduct(pMeans,pSlopes,K); + // Fill first line in result matrix + pResMat->PutDouble(_bRKP ? exp(fIntercept) : fIntercept, K, 0 ); + for (SCSIZE i = 0; i < K; i++) + pResMat->PutDouble(_bRKP ? exp(pSlopes->GetDouble(i)) + : pSlopes->GetDouble(i) , K-1-i, 0); + + if (bStats) + { + double fSSreg = 0.0; + double fSSresid = 0.0; + // re-use memory of Z; + pMatZ->FillDouble(0.0, 0, 0, N-1, 0); + // Z = R * Slopes + lcl_ApplyUpperRightTriangle(pMatX, aVecR, pSlopes, pMatZ, K, true); + // Z = Q * Z, that is Q * R * Slopes = X * Slopes + for (SCSIZE rowp1 = K; rowp1 > 0; rowp1--) + { + lcl_TApplyHouseholderTransformation(pMatX, rowp1-1, pMatZ,N); + } + fSSreg =lcl_GetSumProduct(pMatZ, pMatZ, N); + // re-use Y for residuals, Y = Y-Z + for (SCSIZE col = 0; col < N; col++) + pMatY->PutDouble(pMatY->GetDouble(col) - pMatZ->GetDouble(col), col); + fSSresid = lcl_GetSumProduct(pMatY, pMatY, N); + pResMat->PutDouble(fSSreg, 0, 4); + pResMat->PutDouble(fSSresid, 1, 4); + + double fDegreesFreedom =static_cast<double>( bConstant ? N-K-1 : N-K ); + pResMat->PutDouble(fDegreesFreedom, 1, 3); + + if (fDegreesFreedom == 0.0 || fSSresid == 0.0 || fSSreg == 0.0) + { // exact fit; incl. case observed values Y are identical + pResMat->PutDouble(0.0, 1, 4); // SSresid + // F = (SSreg/K) / (SSresid/df) = #DIV/0! + pResMat->PutError( FormulaError::NotAvailable, 0, 3); // F + // RMSE = sqrt(SSresid / df) = sqrt(0 / df) = 0 + pResMat->PutDouble(0.0, 1, 2); // RMSE + // SigmaSlope[i] = RMSE * sqrt(matrix[i,i]) = 0 * sqrt(...) = 0 + for (SCSIZE i=0; i<K; i++) + pResMat->PutDouble(0.0, K-1-i, 1); + + // SigmaIntercept = RMSE * sqrt(...) = 0 + if (bConstant) + pResMat->PutDouble(0.0, K, 1); //SigmaIntercept + else + pResMat->PutError( FormulaError::NotAvailable, K, 1); + + // R^2 = SSreg / (SSreg + SSresid) = 1.0 + pResMat->PutDouble(1.0, 0, 2); // R^2 + } + else + { + double fFstatistic = (fSSreg / static_cast<double>(K)) + / (fSSresid / fDegreesFreedom); + pResMat->PutDouble(fFstatistic, 0, 3); + + // standard error of estimate = root mean SSE + double fRMSE = sqrt(fSSresid / fDegreesFreedom); + pResMat->PutDouble(fRMSE, 1, 2); + + // standard error of slopes + // = RMSE * sqrt(diagonal element of (R' R)^(-1) ) + // standard error of intercept + // = RMSE * sqrt( Xmean * (R' R)^(-1) * Xmean' + 1/N) + // (R' R)^(-1) = R^(-1) * (R')^(-1). Do not calculate it as + // a whole matrix, but iterate over unit vectors. + // (R' R) ^(-1) is symmetric + KahanSum aSigmaIntercept = 0.0; + double fPart; // for Xmean * single col of (R' R)^(-1) + for (SCSIZE row = 0; row < K; row++) + { + //re-use memory of MatZ + pMatZ->FillDouble(0.0,0,0,K-1,0); // Z = unit vector e + pMatZ->PutDouble(1.0, row); + //Solve R' * Z = e + lcl_SolveWithLowerLeftTriangle(pMatX, aVecR, pMatZ, K, true); + // Solve R * Znew = Zold + lcl_SolveWithUpperRightTriangle(pMatX, aVecR, pMatZ, K, true); + // now Z is column col in (R' R)^(-1) + double fSigmaSlope = fRMSE * sqrt(pMatZ->GetDouble(row)); + pResMat->PutDouble(fSigmaSlope, K-1-row, 1); + if (bConstant) + { + fPart = lcl_GetSumProduct(pMeans, pMatZ, K); + aSigmaIntercept += fPart * pMeans->GetDouble(row); + } + } + if (bConstant) + { + double fSigmaIntercept = fRMSE + * sqrt( (aSigmaIntercept + 1.0 / static_cast<double>(N) ).get() ); + pResMat->PutDouble(fSigmaIntercept, K, 1); + } + else + { + pResMat->PutError( FormulaError::NotAvailable, K, 1); + } + + double fR2 = fSSreg / (fSSreg + fSSresid); + pResMat->PutDouble(fR2, 0, 2); + } + } + PushMatrix(pResMat); + } + } +} + +void ScInterpreter::ScTrend() +{ + CalculateTrendGrowth(false); +} + +void ScInterpreter::ScGrowth() +{ + CalculateTrendGrowth(true); +} + +void ScInterpreter::CalculateTrendGrowth(bool _bGrowth) +{ + sal_uInt8 nParamCount = GetByte(); + if (!MustHaveParamCount( nParamCount, 1, 4 )) + return; + + // optional forth parameter + bool bConstant; + if (nParamCount == 4) + bConstant = GetBool(); + else + bConstant = true; + + // The third parameter may be missing in ODF, although the forth parameter + // is present. Default values depend on data not yet read. + ScMatrixRef pMatNewX; + if (nParamCount >= 3) + { + if (IsMissing()) + { + Pop(); + pMatNewX = nullptr; + } + else + pMatNewX = GetMatrix(); + } + else + pMatNewX = nullptr; + + //In ODF1.2 empty second parameter (which is two ;; ) is allowed + //Defaults will be set in CheckMatrix + ScMatrixRef pMatX; + if (nParamCount >= 2) + { + if (IsMissing()) + { + Pop(); + pMatX = nullptr; + } + else + { + pMatX = GetMatrix(); + } + } + else + pMatX = nullptr; + + ScMatrixRef pMatY = GetMatrix(); + if (!pMatY) + { + PushIllegalParameter(); + return; + } + + // 1 = simple; 2 = multiple with Y as column; 3 = multiple with Y as row + sal_uInt8 nCase; + + SCSIZE nCX, nCY; // number of columns + SCSIZE nRX, nRY; //number of rows + SCSIZE K = 0, N = 0; // K=number of variables X, N=number of data samples + if (!CheckMatrix(_bGrowth,nCase,nCX,nCY,nRX,nRY,K,N,pMatX,pMatY)) + { + PushIllegalParameter(); + return; + } + + // Enough data samples? + if ((bConstant && (N<K+1)) || (!bConstant && (N<K)) || (N<1) || (K<1)) + { + PushIllegalParameter(); + return; + } + + // Set default pMatNewX if necessary + SCSIZE nCXN, nRXN; + SCSIZE nCountXN; + if (!pMatNewX) + { + nCXN = nCX; + nRXN = nRX; + nCountXN = nCXN * nRXN; + pMatNewX = pMatX->Clone(); // pMatX will be changed to X-meanX + } + else + { + pMatNewX->GetDimensions(nCXN, nRXN); + if ((nCase == 2 && K != nCXN) || (nCase == 3 && K != nRXN)) + { + PushIllegalArgument(); + return; + } + nCountXN = nCXN * nRXN; + for (SCSIZE i = 0; i < nCountXN; i++) + if (!pMatNewX->IsValue(i)) + { + PushIllegalArgument(); + return; + } + } + ScMatrixRef pResMat; // size depends on nCase + if (nCase == 1) + pResMat = GetNewMat(nCXN,nRXN, /*bEmpty*/true); + else + { + if (nCase==2) + pResMat = GetNewMat(1,nRXN, /*bEmpty*/true); + else + pResMat = GetNewMat(nCXN,1, /*bEmpty*/true); + } + if (!pResMat) + { + PushError(FormulaError::CodeOverflow); + return; + } + // Uses sum(x-MeanX)^2 and not [sum x^2]-N * MeanX^2 in case bConstant. + // Clone constant matrices, so that Mat = Mat - Mean is possible. + double fMeanY = 0.0; + if (bConstant) + { + ScMatrixRef pCopyX = pMatX->CloneIfConst(); + ScMatrixRef pCopyY = pMatY->CloneIfConst(); + if (!pCopyX || !pCopyY) + { + PushError(FormulaError::MatrixSize); + return; + } + pMatX = pCopyX; + pMatY = pCopyY; + // DeltaY is possible here; DeltaX depends on nCase, so later + fMeanY = lcl_GetMeanOverAll(pMatY, N); + for (SCSIZE i=0; i<N; i++) + { + pMatY->PutDouble( ::rtl::math::approxSub(pMatY->GetDouble(i),fMeanY), i ); + } + } + + if (nCase==1) + { + // calculate simple regression + double fMeanX = 0.0; + if (bConstant) + { // Mat = Mat - Mean + fMeanX = lcl_GetMeanOverAll(pMatX, N); + for (SCSIZE i=0; i<N; i++) + { + pMatX->PutDouble( ::rtl::math::approxSub(pMatX->GetDouble(i),fMeanX), i ); + } + } + double fSumXY = lcl_GetSumProduct(pMatX,pMatY,N); + double fSumX2 = lcl_GetSumProduct(pMatX,pMatX,N); + if (fSumX2==0.0) + { + PushNoValue(); // all x-values are identical + return; + } + double fSlope = fSumXY / fSumX2; + double fHelp; + if (bConstant) + { + double fIntercept = fMeanY - fSlope * fMeanX; + for (SCSIZE i = 0; i < nCountXN; i++) + { + fHelp = pMatNewX->GetDouble(i)*fSlope + fIntercept; + pResMat->PutDouble(_bGrowth ? exp(fHelp) : fHelp, i); + } + } + else + { + for (SCSIZE i = 0; i < nCountXN; i++) + { + fHelp = pMatNewX->GetDouble(i)*fSlope; + pResMat->PutDouble(_bGrowth ? exp(fHelp) : fHelp, i); + } + } + } + else // calculate multiple regression; + { + if (nCase ==2) // Y is column + { + ::std::vector< double> aVecR(N); // for QR decomposition + // Enough memory for needed matrices? + ScMatrixRef pMeans = GetNewMat(K, 1, /*bEmpty*/true); // mean of each column + ScMatrixRef pSlopes = GetNewMat(1,K, /*bEmpty*/true); // from b1 to bK + if (!pMeans || !pSlopes) + { + PushError(FormulaError::CodeOverflow); + return; + } + if (bConstant) + { + lcl_CalculateColumnMeans(pMatX, pMeans, K, N); + lcl_CalculateColumnsDelta(pMatX, pMeans, K, N); + } + if (!lcl_CalculateQRdecomposition(pMatX, aVecR, K, N)) + { + PushNoValue(); + return; + } + // Later on we will divide by elements of aVecR, so make sure + // that they aren't zero. + bool bIsSingular=false; + for (SCSIZE row=0; row < K && !bIsSingular; row++) + bIsSingular = aVecR[row] == 0.0; + if (bIsSingular) + { + PushNoValue(); + return; + } + // Z := Q' Y; Y is overwritten with result Z + for (SCSIZE col = 0; col < K; col++) + { + lcl_ApplyHouseholderTransformation(pMatX, col, pMatY, N); + } + // B = R^(-1) * Q' * Y <=> B = R^(-1) * Z <=> R * B = Z + // result Z should have zeros for index>=K; if not, ignore values + for (SCSIZE col = 0; col < K ; col++) + { + pSlopes->PutDouble( pMatY->GetDouble(col), col); + } + lcl_SolveWithUpperRightTriangle(pMatX, aVecR, pSlopes, K, false); + + // Fill result matrix + lcl_MFastMult(pMatNewX,pSlopes,pResMat,nRXN,K,1); + if (bConstant) + { + double fIntercept = fMeanY - lcl_GetSumProduct(pMeans,pSlopes,K); + for (SCSIZE row = 0; row < nRXN; row++) + pResMat->PutDouble(pResMat->GetDouble(row)+fIntercept, row); + } + if (_bGrowth) + { + for (SCSIZE i = 0; i < nRXN; i++) + pResMat->PutDouble(exp(pResMat->GetDouble(i)), i); + } + } + else + { // nCase == 3, Y is row, all matrices are transposed + + ::std::vector< double> aVecR(N); // for QR decomposition + // Enough memory for needed matrices? + ScMatrixRef pMeans = GetNewMat(1, K, /*bEmpty*/true); // mean of each row + ScMatrixRef pSlopes = GetNewMat(K,1, /*bEmpty*/true); // row from b1 to bK + if (!pMeans || !pSlopes) + { + PushError(FormulaError::CodeOverflow); + return; + } + if (bConstant) + { + lcl_CalculateRowMeans(pMatX, pMeans, N, K); + lcl_CalculateRowsDelta(pMatX, pMeans, N, K); + } + if (!lcl_TCalculateQRdecomposition(pMatX, aVecR, K, N)) + { + PushNoValue(); + return; + } + // Later on we will divide by elements of aVecR, so make sure + // that they aren't zero. + bool bIsSingular=false; + for (SCSIZE row=0; row < K && !bIsSingular; row++) + bIsSingular = aVecR[row] == 0.0; + if (bIsSingular) + { + PushNoValue(); + return; + } + // Z := Q' Y; Y is overwritten with result Z + for (SCSIZE row = 0; row < K; row++) + { + lcl_TApplyHouseholderTransformation(pMatX, row, pMatY, N); + } + // B = R^(-1) * Q' * Y <=> B = R^(-1) * Z <=> R * B = Z + // result Z should have zeros for index>=K; if not, ignore values + for (SCSIZE col = 0; col < K ; col++) + { + pSlopes->PutDouble( pMatY->GetDouble(col), col); + } + lcl_SolveWithUpperRightTriangle(pMatX, aVecR, pSlopes, K, true); + + // Fill result matrix + lcl_MFastMult(pSlopes,pMatNewX,pResMat,1,K,nCXN); + if (bConstant) + { + double fIntercept = fMeanY - lcl_GetSumProduct(pMeans,pSlopes,K); + for (SCSIZE col = 0; col < nCXN; col++) + pResMat->PutDouble(pResMat->GetDouble(col)+fIntercept, col); + } + if (_bGrowth) + { + for (SCSIZE i = 0; i < nCXN; i++) + pResMat->PutDouble(exp(pResMat->GetDouble(i)), i); + } + } + } + PushMatrix(pResMat); +} + +void ScInterpreter::ScMatRef() +{ + // In case it contains relative references resolve them as usual. + Push( *pCur ); + ScAddress aAdr; + PopSingleRef( aAdr ); + + ScRefCellValue aCell(mrDoc, aAdr); + + if (aCell.getType() != CELLTYPE_FORMULA) + { + PushError( FormulaError::NoRef ); + return; + } + + if (aCell.getFormula()->IsRunning()) + { + // Twisted odd corner case where an array element's cell tries to + // access the top left matrix while it is still running, see tdf#88737 + // This is a hackish workaround, not a general solution, the matrix + // isn't available anyway and FormulaError::CircularReference would be set. + PushError( FormulaError::RetryCircular ); + return; + } + + const ScMatrix* pMat = aCell.getFormula()->GetMatrix(); + if (pMat) + { + SCSIZE nCols, nRows; + pMat->GetDimensions( nCols, nRows ); + SCSIZE nC = static_cast<SCSIZE>(aPos.Col() - aAdr.Col()); + SCSIZE nR = static_cast<SCSIZE>(aPos.Row() - aAdr.Row()); +#if 0 + // XXX: this could be an additional change for tdf#145085 to not + // display the URL in a voluntary entered 2-rows array context. + // However, that might as well be used on purpose to implement a check + // on the URL, which existing documents may have done, the more that + // before the accompanying change of + // ScFormulaCell::GetResultDimensions() the cell array was forced to + // two rows. Do not change without compelling reason. Note that this + // repeating top cell is what Excel implements, but it has no + // additional value so probably isn't used there. Exporting to and + // using in Excel though will lose this capability. + if (aCell.mpFormula->GetCode()->IsHyperLink()) + { + // Row 2 element is the URL that is not to be displayed, fake a + // 1-row cell-text-only matrix that is repeated. + assert(nRows == 2); + nR = 0; + } +#endif + if ((nCols <= nC && nCols != 1) || (nRows <= nR && nRows != 1)) + PushNA(); + else + { + const ScMatrixValue nMatVal = pMat->Get( nC, nR); + ScMatValType nMatValType = nMatVal.nType; + + if (ScMatrix::IsNonValueType( nMatValType)) + { + if (ScMatrix::IsEmptyPathType( nMatValType)) + { // result of empty false jump path + nFuncFmtType = SvNumFormatType::LOGICAL; + PushInt(0); + } + else if (ScMatrix::IsEmptyType( nMatValType)) + { + // Not inherited (really?) and display as empty string, not 0. + PushTempToken( new ScEmptyCellToken( false, true)); + } + else + PushString( nMatVal.GetString() ); + } + else + { + // Determine nFuncFmtType type before PushDouble(). + mrDoc.GetNumberFormatInfo(mrContext, nCurFmtType, nCurFmtIndex, aAdr); + nFuncFmtType = nCurFmtType; + nFuncFmtIndex = nCurFmtIndex; + PushDouble(nMatVal.fVal); // handles DoubleError + } + } + } + else + { + // Determine nFuncFmtType type before PushDouble(). + mrDoc.GetNumberFormatInfo(mrContext, nCurFmtType, nCurFmtIndex, aAdr); + nFuncFmtType = nCurFmtType; + nFuncFmtIndex = nCurFmtIndex; + // If not a result matrix, obtain the cell value. + FormulaError nErr = aCell.getFormula()->GetErrCode(); + if (nErr != FormulaError::NONE) + PushError( nErr ); + else if (aCell.getFormula()->IsValue()) + PushDouble(aCell.getFormula()->GetValue()); + else + { + svl::SharedString aVal = aCell.getFormula()->GetString(); + PushString( aVal ); + } + } +} + +void ScInterpreter::ScInfo() +{ + if( !MustHaveParamCount( GetByte(), 1 ) ) + return; + + OUString aStr = GetString().getString(); + ScCellKeywordTranslator::transKeyword(aStr, &ScGlobal::GetLocale(), ocInfo); + if( aStr == "SYSTEM" ) + PushString( SC_INFO_OSVERSION ); + else if( aStr == "OSVERSION" ) +#if (defined LINUX || defined __FreeBSD__) + PushString(Application::GetOSVersion()); +#elif defined MACOSX + // TODO tdf#140286 handle MACOSX version to get result compatible to Excel + PushString("Windows (32-bit) NT 5.01"); +#else // handle Windows (WNT, WIN_NT, WIN32, _WIN32) + // TODO tdf#140286 handle Windows version to get a result compatible to Excel + PushString( "Windows (32-bit) NT 5.01" ); +#endif + else if( aStr == "RELEASE" ) + PushString( ::utl::Bootstrap::getBuildIdData( OUString() ) ); + else if( aStr == "NUMFILE" ) + PushDouble( 1 ); + else if( aStr == "RECALC" ) + PushString( ScResId( mrDoc.GetAutoCalc() ? STR_RECALC_AUTO : STR_RECALC_MANUAL ) ); + else if (aStr == "DIRECTORY" || aStr == "MEMAVAIL" || aStr == "MEMUSED" || aStr == "ORIGIN" || aStr == "TOTMEM") + PushNA(); + else + PushIllegalArgument(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/interpr6.cxx b/sc/source/core/tool/interpr6.cxx new file mode 100644 index 0000000000..8c6cfb449b --- /dev/null +++ b/sc/source/core/tool/interpr6.cxx @@ -0,0 +1,1010 @@ +/* -*- 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 <interpre.hxx> +#include <columnspanset.hxx> +#include <column.hxx> +#include <document.hxx> +#include <cellvalue.hxx> +#include <dociter.hxx> +#include <mtvfunctions.hxx> +#include <scmatrix.hxx> + +#include <arraysumfunctor.hxx> + +#include <formula/token.hxx> + +using namespace formula; + +double const fHalfMachEps = 0.5 * ::std::numeric_limits<double>::epsilon(); + +// The idea how this group of gamma functions is calculated, is +// based on the Cephes library +// online http://www.moshier.net/#Cephes [called 2008-02] + +/** You must ensure fA>0.0 && fX>0.0 + valid results only if fX > fA+1.0 + uses continued fraction with odd items */ +double ScInterpreter::GetGammaContFraction( double fA, double fX ) +{ + + double const fBigInv = ::std::numeric_limits<double>::epsilon(); + double const fBig = 1.0/fBigInv; + double fCount = 0.0; + double fY = 1.0 - fA; + double fDenom = fX + 2.0-fA; + double fPkm1 = fX + 1.0; + double fPkm2 = 1.0; + double fQkm1 = fDenom * fX; + double fQkm2 = fX; + double fApprox = fPkm1/fQkm1; + bool bFinished = false; + do + { + fCount = fCount +1.0; + fY = fY+ 1.0; + const double fNum = fY * fCount; + fDenom = fDenom +2.0; + double fPk = fPkm1 * fDenom - fPkm2 * fNum; + const double fQk = fQkm1 * fDenom - fQkm2 * fNum; + if (fQk != 0.0) + { + const double fR = fPk/fQk; + bFinished = (fabs( (fApprox - fR)/fR ) <= fHalfMachEps); + fApprox = fR; + } + fPkm2 = fPkm1; + fPkm1 = fPk; + fQkm2 = fQkm1; + fQkm1 = fQk; + if (fabs(fPk) > fBig) + { + // reduce a fraction does not change the value + fPkm2 = fPkm2 * fBigInv; + fPkm1 = fPkm1 * fBigInv; + fQkm2 = fQkm2 * fBigInv; + fQkm1 = fQkm1 * fBigInv; + } + } while (!bFinished && fCount<10000); + // most iterations, if fX==fAlpha+1.0; approx sqrt(fAlpha) iterations then + if (!bFinished) + { + SetError(FormulaError::NoConvergence); + } + return fApprox; +} + +/** You must ensure fA>0.0 && fX>0.0 + valid results only if fX <= fA+1.0 + uses power series */ +double ScInterpreter::GetGammaSeries( double fA, double fX ) +{ + double fDenomfactor = fA; + double fSummand = 1.0/fA; + double fSum = fSummand; + int nCount=1; + do + { + fDenomfactor = fDenomfactor + 1.0; + fSummand = fSummand * fX/fDenomfactor; + fSum = fSum + fSummand; + nCount = nCount+1; + } while ( fSummand/fSum > fHalfMachEps && nCount<=10000); + // large amount of iterations will be carried out for huge fAlpha, even + // if fX <= fAlpha+1.0 + if (nCount>10000) + { + SetError(FormulaError::NoConvergence); + } + return fSum; +} + +/** You must ensure fA>0.0 && fX>0.0) */ +double ScInterpreter::GetLowRegIGamma( double fA, double fX ) +{ + double fLnFactor = fA * log(fX) - fX - GetLogGamma(fA); + double fFactor = exp(fLnFactor); // Do we need more accuracy than exp(ln()) has? + if (fX>fA+1.0) // includes fX>1.0; 1-GetUpRegIGamma, continued fraction + return 1.0 - fFactor * GetGammaContFraction(fA,fX); + else // fX<=1.0 || fX<=fA+1.0, series + return fFactor * GetGammaSeries(fA,fX); +} + +/** You must ensure fA>0.0 && fX>0.0) */ +double ScInterpreter::GetUpRegIGamma( double fA, double fX ) +{ + + double fLnFactor= fA*log(fX)-fX-GetLogGamma(fA); + double fFactor = exp(fLnFactor); //Do I need more accuracy than exp(ln()) has?; + if (fX>fA+1.0) // includes fX>1.0 + return fFactor * GetGammaContFraction(fA,fX); + else //fX<=1 || fX<=fA+1, 1-GetLowRegIGamma, series + return 1.0 -fFactor * GetGammaSeries(fA,fX); +} + +/** Gamma distribution, probability density function. + fLambda is "scale" parameter + You must ensure fAlpha>0.0 and fLambda>0.0 */ +double ScInterpreter::GetGammaDistPDF( double fX, double fAlpha, double fLambda ) +{ + if (fX < 0.0) + return 0.0; // see ODFF + else if (fX == 0) + // in this case 0^0 isn't zero + { + if (fAlpha < 1.0) + { + SetError(FormulaError::DivisionByZero); // should be #DIV/0 + return HUGE_VAL; + } + else if (fAlpha == 1) + { + return (1.0 / fLambda); + } + else + { + return 0.0; + } + } + else + { + double fXr = fX / fLambda; + // use exp(ln()) only for large arguments because of less accuracy + if (fXr > 1.0) + { + const double fLogDblMax = log( ::std::numeric_limits<double>::max()); + if (log(fXr) * (fAlpha-1.0) < fLogDblMax && fAlpha < fMaxGammaArgument) + { + return pow( fXr, fAlpha-1.0) * exp(-fXr) / fLambda / GetGamma(fAlpha); + } + else + { + return exp( (fAlpha-1.0) * log(fXr) - fXr - log(fLambda) - GetLogGamma(fAlpha)); + } + } + else // fXr near to zero + { + if (fAlpha<fMaxGammaArgument) + { + return pow( fXr, fAlpha-1.0) * exp(-fXr) / fLambda / GetGamma(fAlpha); + } + else + { + return pow( fXr, fAlpha-1.0) * exp(-fXr) / fLambda / exp( GetLogGamma(fAlpha)); + } + } + } +} + +/** Gamma distribution, cumulative distribution function. + fLambda is "scale" parameter + You must ensure fAlpha>0.0 and fLambda>0.0 */ +double ScInterpreter::GetGammaDist( double fX, double fAlpha, double fLambda ) +{ + if (fX <= 0.0) + return 0.0; + else + return GetLowRegIGamma( fAlpha, fX / fLambda); +} + +namespace { + +class NumericCellAccumulator +{ + KahanSum maSum; + FormulaError mnError; + +public: + NumericCellAccumulator() : maSum(0.0), mnError(FormulaError::NONE) {} + + void operator() (const sc::CellStoreType::value_type& rNode, size_t nOffset, size_t nDataSize) + { + switch (rNode.type) + { + case sc::element_type_numeric: + { + if (nDataSize == 0) + return; + + const double *p = &sc::numeric_block::at(*rNode.data, nOffset); + maSum += sc::op::sumArray(p, nDataSize); + break; + } + + case sc::element_type_formula: + { + sc::formula_block::const_iterator it = sc::formula_block::begin(*rNode.data); + std::advance(it, nOffset); + sc::formula_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + for (; it != itEnd; ++it) + { + double fVal = 0.0; + FormulaError nErr = FormulaError::NONE; + ScFormulaCell& rCell = *(*it); + if (!rCell.GetErrorOrValue(nErr, fVal)) + // The cell has neither error nor value. Perhaps string result. + continue; + + if (nErr != FormulaError::NONE) + { + // Cell has error - skip all the rest + mnError = nErr; + return; + } + + maSum += fVal; + } + } + break; + default: + ; + } + } + + FormulaError getError() const { return mnError; } + const KahanSum& getResult() const { return maSum; } +}; + +class NumericCellCounter +{ + size_t mnCount; +public: + NumericCellCounter() : mnCount(0) {} + + void operator() (const sc::CellStoreType::value_type& rNode, size_t nOffset, size_t nDataSize) + { + switch (rNode.type) + { + case sc::element_type_numeric: + mnCount += nDataSize; + break; + case sc::element_type_formula: + { + sc::formula_block::const_iterator it = sc::formula_block::begin(*rNode.data); + std::advance(it, nOffset); + sc::formula_block::const_iterator itEnd = it; + std::advance(itEnd, nDataSize); + for (; it != itEnd; ++it) + { + ScFormulaCell& rCell = **it; + if (rCell.IsValueNoError()) + ++mnCount; + } + } + break; + default: + ; + } + } + + size_t getCount() const { return mnCount; } +}; + +class FuncCount : public sc::ColumnSpanSet::ColumnAction +{ + const ScInterpreterContext& mrContext; + sc::ColumnBlockConstPosition maPos; + ScColumn* mpCol; + size_t mnCount; + sal_uInt32 mnNumFmt; + +public: + FuncCount(const ScInterpreterContext& rContext) : mrContext(rContext), mpCol(nullptr), mnCount(0), mnNumFmt(0) {} + + virtual void startColumn(ScColumn* pCol) override + { + mpCol = pCol; + mpCol->InitBlockPosition(maPos); + } + + virtual void execute(SCROW nRow1, SCROW nRow2, bool bVal) override + { + if (!bVal) + return; + + NumericCellCounter aFunc; + maPos.miCellPos = sc::ParseBlock(maPos.miCellPos, mpCol->GetCellStore(), aFunc, nRow1, nRow2); + mnCount += aFunc.getCount(); + mnNumFmt = mpCol->GetNumberFormat(mrContext, nRow2); + }; + + size_t getCount() const { return mnCount; } + sal_uInt32 getNumberFormat() const { return mnNumFmt; } +}; + +class FuncSum : public sc::ColumnSpanSet::ColumnAction +{ + const ScInterpreterContext& mrContext; + sc::ColumnBlockConstPosition maPos; + ScColumn* mpCol; + KahanSum mfSum; + FormulaError mnError; + sal_uInt32 mnNumFmt; + +public: + FuncSum(const ScInterpreterContext& rContext) : mrContext(rContext), mpCol(nullptr), mfSum(0.0), mnError(FormulaError::NONE), mnNumFmt(0) {} + + virtual void startColumn(ScColumn* pCol) override + { + mpCol = pCol; + mpCol->InitBlockPosition(maPos); + } + + virtual void execute(SCROW nRow1, SCROW nRow2, bool bVal) override + { + if (!bVal) + return; + + if (mnError != FormulaError::NONE) + return; + + NumericCellAccumulator aFunc; + maPos.miCellPos = sc::ParseBlock(maPos.miCellPos, mpCol->GetCellStore(), aFunc, nRow1, nRow2); + mnError = aFunc.getError(); + if (mnError != FormulaError::NONE) + return; + + + mfSum += aFunc.getResult(); + mnNumFmt = mpCol->GetNumberFormat(mrContext, nRow2); + }; + + FormulaError getError() const { return mnError; } + const KahanSum& getSum() const { return mfSum; } + sal_uInt32 getNumberFormat() const { return mnNumFmt; } +}; + +} + +static void IterateMatrix( + const ScMatrixRef& pMat, ScIterFunc eFunc, bool bTextAsZero, SubtotalFlags nSubTotalFlags, + sal_uLong& rCount, SvNumFormatType& rFuncFmtType, KahanSum& fRes ) +{ + if (!pMat) + return; + + const bool bIgnoreErrVal = bool(nSubTotalFlags & SubtotalFlags::IgnoreErrVal); + rFuncFmtType = SvNumFormatType::NUMBER; + switch (eFunc) + { + case ifAVERAGE: + case ifSUM: + { + ScMatrix::KahanIterateResult aRes = pMat->Sum(bTextAsZero, bIgnoreErrVal); + fRes += aRes.maAccumulator; + rCount += aRes.mnCount; + } + break; + case ifCOUNT: + rCount += pMat->Count(bTextAsZero, false); // do not count error values + break; + case ifCOUNT2: + /* TODO: what is this supposed to be with bIgnoreErrVal? */ + rCount += pMat->Count(true, true); // do count error values + break; + case ifPRODUCT: + { + ScMatrix::DoubleIterateResult aRes = pMat->Product(bTextAsZero, bIgnoreErrVal); + fRes *= aRes.maAccumulator; + rCount += aRes.mnCount; + } + break; + case ifSUMSQ: + { + ScMatrix::KahanIterateResult aRes = pMat->SumSquare(bTextAsZero, bIgnoreErrVal); + fRes += aRes.maAccumulator; + rCount += aRes.mnCount; + } + break; + default: + ; + } +} + +size_t ScInterpreter::GetRefListArrayMaxSize( short nParamCount ) +{ + size_t nSize = 0; + if (IsInArrayContext()) + { + for (short i=1; i <= nParamCount; ++i) + { + if (GetStackType(i) == svRefList) + { + const ScRefListToken* p = dynamic_cast<const ScRefListToken*>(pStack[sp - i]); + if (p && p->IsArrayResult() && p->GetRefList()->size() > nSize) + nSize = p->GetRefList()->size(); + } + } + } + return nSize; +} + +static double lcl_IterResult( ScIterFunc eFunc, double fRes, sal_uLong nCount ) +{ + switch( eFunc ) + { + case ifAVERAGE: + fRes = sc::div( fRes, nCount); + break; + case ifCOUNT2: + case ifCOUNT: + fRes = nCount; + break; + case ifPRODUCT: + if ( !nCount ) + fRes = 0.0; + break; + default: + ; // nothing + } + return fRes; +} + +void ScInterpreter::IterateParameters( ScIterFunc eFunc, bool bTextAsZero ) +{ + short nParamCount = GetByte(); + const SCSIZE nMatRows = GetRefListArrayMaxSize( nParamCount); + ScMatrixRef xResMat, xResCount; + const double ResInitVal = (eFunc == ifPRODUCT) ? 1.0 : 0.0; + KahanSum fRes = ResInitVal; + double fVal = 0.0; + sal_uLong nCount = 0; + ScAddress aAdr; + ScRange aRange; + size_t nRefInList = 0; + size_t nRefArrayPos = std::numeric_limits<size_t>::max(); + if ( nGlobalError != FormulaError::NONE && ( eFunc == ifCOUNT2 || eFunc == ifCOUNT || + ( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) ) ) + nGlobalError = FormulaError::NONE; + while (nParamCount-- > 0) + { + switch (GetStackType()) + { + case svString: + { + if( eFunc == ifCOUNT ) + { + OUString aStr = PopString().getString(); + if ( bTextAsZero ) + nCount++; + else + { + // Only check if string can be converted to number, no + // error propagation. + FormulaError nErr = nGlobalError; + nGlobalError = FormulaError::NONE; + ConvertStringToValue( aStr ); + if (nGlobalError == FormulaError::NONE) + ++nCount; + nGlobalError = nErr; + } + } + else + { + Pop(); + switch ( eFunc ) + { + case ifAVERAGE: + case ifSUM: + case ifSUMSQ: + case ifPRODUCT: + { + if ( bTextAsZero ) + { + nCount++; + if ( eFunc == ifPRODUCT ) + fRes = 0.0; + } + else + { + while (nParamCount-- > 0) + PopError(); + SetError( FormulaError::NoValue ); + } + } + break; + default: + nCount++; + } + } + } + break; + case svDouble : + fVal = GetDouble(); + nCount++; + switch( eFunc ) + { + case ifAVERAGE: + case ifSUM: fRes += fVal; break; + case ifSUMSQ: fRes += fVal * fVal; break; + case ifPRODUCT: fRes *= fVal; break; + default: ; // nothing + } + nFuncFmtType = SvNumFormatType::NUMBER; + break; + case svExternalSingleRef: + { + ScExternalRefCache::TokenRef pToken; + ScExternalRefCache::CellFormat aFmt; + PopExternalSingleRef(pToken, &aFmt); + if ( nGlobalError != FormulaError::NONE && ( eFunc == ifCOUNT2 || eFunc == ifCOUNT || + ( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) ) ) + { + nGlobalError = FormulaError::NONE; + if ( eFunc == ifCOUNT2 && !( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) ) + ++nCount; + break; + } + + if (!pToken) + break; + + StackVar eType = pToken->GetType(); + if (eFunc == ifCOUNT2) + { + if ( eType != svEmptyCell && + ( ( pToken->GetOpCode() != ocSubTotal && + pToken->GetOpCode() != ocAggregate ) || + ( mnSubTotalFlags & SubtotalFlags::IgnoreNestedStAg ) ) ) + nCount++; + if (nGlobalError != FormulaError::NONE) + nGlobalError = FormulaError::NONE; + } + else if (eType == svDouble) + { + nCount++; + fVal = pToken->GetDouble(); + if (aFmt.mbIsSet) + { + nFuncFmtType = aFmt.mnType; + nFuncFmtIndex = aFmt.mnIndex; + } + switch( eFunc ) + { + case ifAVERAGE: + case ifSUM: fRes += fVal; break; + case ifSUMSQ: fRes += fVal * fVal; break; + case ifPRODUCT: fRes *= fVal; break; + case ifCOUNT: + if ( nGlobalError != FormulaError::NONE ) + { + nGlobalError = FormulaError::NONE; + nCount--; + } + break; + default: ; // nothing + } + } + else if (bTextAsZero && eType == svString) + { + nCount++; + if ( eFunc == ifPRODUCT ) + fRes = 0.0; + } + } + break; + case svSingleRef : + { + PopSingleRef( aAdr ); + if (nGlobalError == FormulaError::NoRef) + { + PushError( FormulaError::NoRef); + return; + } + + if ( ( ( mnSubTotalFlags & SubtotalFlags::IgnoreFiltered ) && + mrDoc.RowFiltered( aAdr.Row(), aAdr.Tab() ) ) || + ( ( mnSubTotalFlags & SubtotalFlags::IgnoreHidden ) && + mrDoc.RowHidden( aAdr.Row(), aAdr.Tab() ) ) ) + { + break; + } + if ( nGlobalError != FormulaError::NONE && ( eFunc == ifCOUNT2 || eFunc == ifCOUNT || + ( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) ) ) + { + nGlobalError = FormulaError::NONE; + if ( eFunc == ifCOUNT2 && !( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) ) + ++nCount; + break; + } + ScRefCellValue aCell(mrDoc, aAdr); + if (!aCell.isEmpty()) + { + if( eFunc == ifCOUNT2 ) + { + CellType eCellType = aCell.getType(); + if ( eCellType != CELLTYPE_NONE ) + nCount++; + if ( nGlobalError != FormulaError::NONE ) + nGlobalError = FormulaError::NONE; + } + else if (aCell.hasNumeric()) + { + fVal = GetCellValue(aAdr, aCell); + if (nGlobalError != FormulaError::NONE) + { + if (eFunc == ifCOUNT || (mnSubTotalFlags & SubtotalFlags::IgnoreErrVal)) + nGlobalError = FormulaError::NONE; + break; + } + nCount++; + CurFmtToFuncFmt(); + switch( eFunc ) + { + case ifAVERAGE: + case ifSUM: fRes += fVal; break; + case ifSUMSQ: fRes += fVal * fVal; break; + case ifPRODUCT: fRes *= fVal; break; + default: ; // nothing + } + } + else if (bTextAsZero && aCell.hasString()) + { + nCount++; + if ( eFunc == ifPRODUCT ) + fRes = 0.0; + } + } + } + break; + case svRefList : + { + const ScRefListToken* p = dynamic_cast<const ScRefListToken*>(pStack[sp-1]); + if (p && p->IsArrayResult()) + { + nRefArrayPos = nRefInList; + // The "one value to all references of an array" seems to + // be what Excel does if there are other types than just + // arrays of references. + if (!xResMat) + { + // Create and init all elements with current value. + assert(nMatRows > 0); + xResMat = GetNewMat( 1, nMatRows, true); + xResMat->FillDouble( fRes.get(), 0,0, 0,nMatRows-1); + if (eFunc != ifSUM) + { + xResCount = GetNewMat( 1, nMatRows, true); + xResCount->FillDouble( nCount, 0,0, 0,nMatRows-1); + } + } + else + { + // Current value and values from vector are operands + // for each vector position. + if (nCount && xResCount) + { + for (SCSIZE i=0; i < nMatRows; ++i) + { + xResCount->PutDouble( xResCount->GetDouble(0,i) + nCount, 0,i); + } + } + if (fRes != ResInitVal) + { + for (SCSIZE i=0; i < nMatRows; ++i) + { + double fVecRes = xResMat->GetDouble(0,i); + if (eFunc == ifPRODUCT) + fVecRes *= fRes.get(); + else + fVecRes += fRes.get(); + xResMat->PutDouble( fVecRes, 0,i); + } + } + } + fRes = ResInitVal; + nCount = 0; + } + } + [[fallthrough]]; + case svDoubleRef : + { + PopDoubleRef( aRange, nParamCount, nRefInList); + if (nGlobalError == FormulaError::NoRef) + { + PushError( FormulaError::NoRef); + return; + } + + if ( nGlobalError != FormulaError::NONE && ( eFunc == ifCOUNT2 || eFunc == ifCOUNT || + ( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) ) ) + { + nGlobalError = FormulaError::NONE; + if ( eFunc == ifCOUNT2 && !( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) ) + ++nCount; + if ( eFunc == ifCOUNT2 || eFunc == ifCOUNT ) + break; + } + if( eFunc == ifCOUNT2 ) + { + ScCellIterator aIter( mrDoc, aRange, mnSubTotalFlags ); + for (bool bHas = aIter.first(); bHas; bHas = aIter.next()) + { + if ( !aIter.isEmpty() ) + { + ++nCount; + } + } + + if ( nGlobalError != FormulaError::NONE ) + nGlobalError = FormulaError::NONE; + } + else if (((eFunc == ifSUM && !bCalcAsShown) || eFunc == ifCOUNT ) + && mnSubTotalFlags == SubtotalFlags::NONE) + { + // Use fast span set array method. + // ifSUM with bCalcAsShown has to use the slow bells and + // whistles ScValueIterator below. + sc::RangeColumnSpanSet aSet( aRange ); + + if ( eFunc == ifSUM ) + { + FuncSum aAction(mrContext); + aSet.executeColumnAction( mrDoc, aAction ); + FormulaError nErr = aAction.getError(); + if ( nErr != FormulaError::NONE ) + { + PushError( nErr ); + return; + } + fRes += aAction.getSum(); + + // Get the number format of the last iterated cell. + nFuncFmtIndex = aAction.getNumberFormat(); + } + else + { + FuncCount aAction(mrContext); + aSet.executeColumnAction(mrDoc, aAction); + nCount += aAction.getCount(); + + // Get the number format of the last iterated cell. + nFuncFmtIndex = aAction.getNumberFormat(); + } + + nFuncFmtType = mrContext.GetNumberFormatType( nFuncFmtIndex ); + } + else + { + ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags, bTextAsZero ); + FormulaError nErr = FormulaError::NONE; + if (aValIter.GetFirst(fVal, nErr)) + { + // placed the loop on the inside for performance reasons: + aValIter.GetCurNumFmtInfo( nFuncFmtType, nFuncFmtIndex ); + switch( eFunc ) + { + case ifAVERAGE: + case ifSUM: + if ( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) + { + do + { + if ( nErr == FormulaError::NONE ) + { + SetError(nErr); + fRes += fVal; + nCount++; + } + } + while (aValIter.GetNext(fVal, nErr)); + } + else + { + do + { + SetError(nErr); + fRes += fVal; + nCount++; + } + while (aValIter.GetNext(fVal, nErr)); + } + break; + case ifSUMSQ: + if ( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) + { + do + { + if ( nErr == FormulaError::NONE ) + { + SetError(nErr); + fRes += fVal * fVal; + nCount++; + } + } + while (aValIter.GetNext(fVal, nErr)); + } + else + { + do + { + SetError(nErr); + fRes += fVal * fVal; + nCount++; + } + while (aValIter.GetNext(fVal, nErr)); + } + break; + case ifPRODUCT: + do + { + if ( !( nErr != FormulaError::NONE && ( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) ) ) + { + SetError(nErr); + fRes *= fVal; + nCount++; + } + } + while (aValIter.GetNext(fVal, nErr)); + break; + case ifCOUNT: + do + { + if ( nErr == FormulaError::NONE ) + nCount++; + } + while (aValIter.GetNext(fVal, nErr)); + break; + default: ; // nothing + } + SetError( nErr ); + } + } + if (nRefArrayPos != std::numeric_limits<size_t>::max()) + { + // Update vector element with current value. + if (xResCount) + xResCount->PutDouble( xResCount->GetDouble(0,nRefArrayPos) + nCount, 0,nRefArrayPos); + double fVecRes = xResMat->GetDouble(0,nRefArrayPos); + if (eFunc == ifPRODUCT) + fVecRes *= fRes.get(); + else + fVecRes += fRes.get(); + xResMat->PutDouble( fVecRes, 0,nRefArrayPos); + // Reset. + fRes = ResInitVal; + nCount = 0; + nRefArrayPos = std::numeric_limits<size_t>::max(); + } + } + break; + case svExternalDoubleRef: + { + ScMatrixRef pMat; + PopExternalDoubleRef(pMat); + if ( nGlobalError != FormulaError::NONE && !( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) ) + break; + + IterateMatrix( pMat, eFunc, bTextAsZero, mnSubTotalFlags, nCount, nFuncFmtType, fRes ); + } + break; + case svMatrix : + { + ScMatrixRef pMat = PopMatrix(); + + IterateMatrix( pMat, eFunc, bTextAsZero, mnSubTotalFlags, nCount, nFuncFmtType, fRes ); + } + break; + case svError: + { + PopError(); + if ( eFunc == ifCOUNT || ( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) ) + { + nGlobalError = FormulaError::NONE; + } + else if ( eFunc == ifCOUNT2 && !( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) ) + { + nCount++; + nGlobalError = FormulaError::NONE; + } + } + break; + default : + while (nParamCount-- > 0) + PopError(); + SetError(FormulaError::IllegalParameter); + } + } + + // A boolean return type makes no sense on sums et al. + // Counts are always numbers. + if( nFuncFmtType == SvNumFormatType::LOGICAL || eFunc == ifCOUNT || eFunc == ifCOUNT2 ) + nFuncFmtType = SvNumFormatType::NUMBER; + + if (xResMat) + { + // Include value of last non-references-array type and calculate final result. + for (SCSIZE i=0; i < nMatRows; ++i) + { + sal_uLong nVecCount = (xResCount ? nCount + xResCount->GetDouble(0,i) : nCount); + double fVecRes = xResMat->GetDouble(0,i); + if (eFunc == ifPRODUCT) + fVecRes *= fRes.get(); + else + fVecRes += fRes.get(); + fVecRes = lcl_IterResult( eFunc, fVecRes, nVecCount); + xResMat->PutDouble( fVecRes, 0,i); + } + PushMatrix( xResMat); + } + else + { + PushDouble( lcl_IterResult( eFunc, fRes.get(), nCount)); + } +} + +void ScInterpreter::ScSumSQ() +{ + IterateParameters( ifSUMSQ ); +} + +void ScInterpreter::ScSum() +{ + IterateParameters( ifSUM ); +} + +void ScInterpreter::ScProduct() +{ + IterateParameters( ifPRODUCT ); +} + +void ScInterpreter::ScAverage( bool bTextAsZero ) +{ + IterateParameters( ifAVERAGE, bTextAsZero ); +} + +void ScInterpreter::ScCount() +{ + IterateParameters( ifCOUNT ); +} + +void ScInterpreter::ScCount2() +{ + IterateParameters( ifCOUNT2 ); +} + +/** + * The purpose of RAWSUBTRACT() is exactly to not apply any error correction, approximation etc. + * But use the "raw" IEEE 754 double subtraction. + * So no Kahan summation + */ +void ScInterpreter::ScRawSubtract() +{ + short nParamCount = GetByte(); + if (!MustHaveParamCountMin( nParamCount, 2)) + return; + + // Reverse stack to process arguments from left to right. + ReverseStack( nParamCount); + // Obtain the minuend. + double fRes = GetDouble(); + + while (nGlobalError == FormulaError::NONE && --nParamCount > 0) + { + // Simple single values without matrix support. + fRes -= GetDouble(); + } + while (nParamCount-- > 0) + PopError(); + + PushDouble( fRes); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/interpr7.cxx b/sc/source/core/tool/interpr7.cxx new file mode 100644 index 0000000000..ecb4ea3463 --- /dev/null +++ b/sc/source/core/tool/interpr7.cxx @@ -0,0 +1,557 @@ +/* -*- 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/. + */ + +#include <interpre.hxx> +#include <jumpmatrix.hxx> +#include <formulacell.hxx> +#include <scmatrix.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/character.hxx> +#include <formula/errorcodes.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/linkmgr.hxx> +#include <tools/urlobj.hxx> + +#include <officecfg/Office/Common.hxx> +#include <libxml/xpath.h> +#include <datastreamgettime.hxx> +#include <dpobject.hxx> +#include <document.hxx> +#include <tokenarray.hxx> +#include <webservicelink.hxx> + +#include <sc.hrc> + +#include <cstring> +#include <memory> +#include <string_view> +#include <libxml/parser.h> + +using namespace com::sun::star; + +// TODO: Add new methods for ScInterpreter here. + +void ScInterpreter::ScFilterXML() +{ + sal_uInt8 nParamCount = GetByte(); + if (!MustHaveParamCount( nParamCount, 2 ) ) + return; + + SCSIZE nMatCols = 1, nMatRows = 1, nNode = 0; + // In array/matrix context node elements' results are to be + // subsequently stored. Check this before obtaining any argument from + // the stack so the stack type can be used. + if (pJumpMatrix || IsInArrayContext()) + { + if (pJumpMatrix) + { + // Single result, GetString() will retrieve the corresponding + // argument and JumpMatrix() will store it at the proper + // position. Note that nMatCols and nMatRows are still 1. + SCSIZE nCurCol = 0, nCurRow = 0; + pJumpMatrix->GetPos( nCurCol, nCurRow); + nNode = nCurRow; + } + else if (bMatrixFormula) + { + // If there is no formula cell then continue with a single + // result. + if (pMyFormulaCell) + { + SCCOL nCols; + SCROW nRows; + pMyFormulaCell->GetMatColsRows( nCols, nRows); + nMatCols = nCols; + nMatRows = nRows; + } + } + else if (GetStackType() == formula::svMatrix) + { + const ScMatrix* pPathMatrix = pStack[sp-1]->GetMatrix(); + if (!pPathMatrix) + { + PushIllegalParameter(); + return; + } + pPathMatrix->GetDimensions( nMatCols, nMatRows); + + /* TODO: it is unclear what should happen if there are + * different path arguments in matrix elements. We may have to + * evaluate each, and for repeated identical paths use + * subsequent nodes. As is, the path at 0,0 is used as obtained + * by GetString(). */ + + } + } + if (!nMatCols || !nMatRows) + { + PushNoValue(); + return; + } + + OUString aXPathExpression = GetString().getString(); + OUString aString = GetString().getString(); + if(aString.isEmpty() || aXPathExpression.isEmpty()) + { + PushError( FormulaError::NoValue ); + return; + } + + OString aOXPathExpression = OUStringToOString( aXPathExpression, RTL_TEXTENCODING_UTF8 ); + const char* pXPathExpr = aOXPathExpression.getStr(); + OString aOString = OUStringToOString( aString, RTL_TEXTENCODING_UTF8 ); + const char* pXML = aOString.getStr(); + + std::shared_ptr<xmlParserCtxt> pContext( + xmlNewParserCtxt(), xmlFreeParserCtxt ); + + std::shared_ptr<xmlDoc> pDoc( xmlParseMemory( pXML, aOString.getLength() ), + xmlFreeDoc ); + + if(!pDoc) + { + PushError( FormulaError::NoValue ); + return; + } + + std::shared_ptr<xmlXPathContext> pXPathCtx( xmlXPathNewContext(pDoc.get()), + xmlXPathFreeContext ); + + std::shared_ptr<xmlXPathObject> pXPathObj( xmlXPathEvalExpression(BAD_CAST(pXPathExpr), pXPathCtx.get()), + xmlXPathFreeObject ); + + if(!pXPathObj) + { + PushError( FormulaError::NoValue ); + return; + } + + switch(pXPathObj->type) + { + case XPATH_UNDEFINED: + PushNoValue(); + break; + case XPATH_NODESET: + { + xmlNodeSetPtr pNodeSet = pXPathObj->nodesetval; + if(!pNodeSet) + { + PushError( FormulaError::NoValue ); + return; + } + + const size_t nSize = pNodeSet->nodeNr; + if (nNode >= nSize) + { + // For pJumpMatrix + PushError( FormulaError::NotAvailable); + return; + } + + /* TODO: for nMatCols>1 IF stack type is svMatrix, i.e. + * pPathMatrix!=nullptr, we may want a result matrix with + * nMatCols columns as well, but clarify first how to treat + * differing path elements. */ + + ScMatrixRef xResMat; + if (nMatRows > 1) + { + xResMat = GetNewMat( 1, nMatRows, true); + if (!xResMat) + { + PushError( FormulaError::CodeOverflow); + return; + } + } + + for ( ; nNode < nMatRows; ++nNode) + { + if( nSize > nNode ) + { + OUString aResult; + if(pNodeSet->nodeTab[nNode]->type == XML_NAMESPACE_DECL) + { + xmlNsPtr ns = reinterpret_cast<xmlNsPtr>(pNodeSet->nodeTab[nNode]); + xmlNodePtr cur = reinterpret_cast<xmlNodePtr>(ns->next); + std::shared_ptr<xmlChar> pChar2(xmlNodeGetContent(cur), xmlFree); + aResult = OStringToOUString(std::string_view(reinterpret_cast<char*>(pChar2.get())), RTL_TEXTENCODING_UTF8); + } + else + { + xmlNodePtr cur = pNodeSet->nodeTab[nNode]; + std::shared_ptr<xmlChar> pChar2(xmlNodeGetContent(cur), xmlFree); + aResult = OStringToOUString(std::string_view(reinterpret_cast<char*>(pChar2.get())), RTL_TEXTENCODING_UTF8); + } + if (xResMat) + xResMat->PutString( mrStrPool.intern( aResult), 0, nNode); + else + PushString(aResult); + } + else + { + if (xResMat) + xResMat->PutError( FormulaError::NotAvailable, 0, nNode); + else + PushError( FormulaError::NotAvailable ); + } + } + if (xResMat) + PushMatrix( xResMat); + } + break; + case XPATH_BOOLEAN: + { + bool bVal = pXPathObj->boolval != 0; + PushDouble(double(bVal)); + } + break; + case XPATH_NUMBER: + { + double fVal = pXPathObj->floatval; + PushDouble(fVal); + } + break; + case XPATH_STRING: + PushString(OUString::createFromAscii(reinterpret_cast<char*>(pXPathObj->stringval))); + break; +#if LIBXML_VERSION < 21000 || defined(LIBXML_XPTR_LOCS_ENABLED) + case XPATH_POINT: + PushNoValue(); + break; + case XPATH_RANGE: + PushNoValue(); + break; + case XPATH_LOCATIONSET: + PushNoValue(); + break; +#endif + case XPATH_USERS: + PushNoValue(); + break; + case XPATH_XSLT_TREE: + PushNoValue(); + break; + + } +} + +static ScWebServiceLink* lcl_GetWebServiceLink(const sfx2::LinkManager* pLinkMgr, std::u16string_view rURL) +{ + size_t nCount = pLinkMgr->GetLinks().size(); + for (size_t i=0; i<nCount; ++i) + { + ::sfx2::SvBaseLink* pBase = pLinkMgr->GetLinks()[i].get(); + if (ScWebServiceLink* pLink = dynamic_cast<ScWebServiceLink*>(pBase)) + { + if (pLink->GetURL() == rURL) + return pLink; + } + } + + return nullptr; +} + +static bool lcl_FunctionAccessLoadWebServiceLink( OUString& rResult, ScDocument* pDoc, const OUString& rURI ) +{ + // For FunctionAccess service always force a changed data update. + ScWebServiceLink aLink( pDoc, rURI); + if (aLink.DataChanged( OUString(), css::uno::Any()) != sfx2::SvBaseLink::UpdateResult::SUCCESS) + return false; + + if (!aLink.HasResult()) + return false; + + rResult = aLink.GetResult(); + + return true; +} + +void ScInterpreter::ScWebservice() +{ + sal_uInt8 nParamCount = GetByte(); + if (!MustHaveParamCount( nParamCount, 1 ) ) + return; + + OUString aURI = GetString().getString(); + + if (aURI.isEmpty()) + { + PushError( FormulaError::NoValue ); + return; + } + + INetURLObject aObj(aURI, INetProtocol::File); + INetProtocol eProtocol = aObj.GetProtocol(); + if (eProtocol != INetProtocol::Http && eProtocol != INetProtocol::Https) + { + PushError(FormulaError::NoValue); + return; + } + + if (!mpLinkManager) + { + if (!mrDoc.IsFunctionAccess() || mrDoc.HasLinkFormulaNeedingCheck()) + { + PushError( FormulaError::NoValue); + } + else + { + OUString aResult; + if (lcl_FunctionAccessLoadWebServiceLink( aResult, &mrDoc, aURI)) + PushString( aResult); + else + PushError( FormulaError::NoValue); + } + return; + } + + // Need to reinterpret after loading (build links) + pArr->AddRecalcMode( ScRecalcMode::ONLOAD_LENIENT ); + + // while the link is not evaluated, idle must be disabled (to avoid circular references) + bool bOldEnabled = mrDoc.IsIdleEnabled(); + mrDoc.EnableIdle(false); + + // Get/ Create link object + ScWebServiceLink* pLink = lcl_GetWebServiceLink(mpLinkManager, aURI); + + bool bWasError = (pMyFormulaCell && pMyFormulaCell->GetRawError() != FormulaError::NONE); + + if (!pLink) + { + pLink = new ScWebServiceLink(&mrDoc, aURI); + mpLinkManager->InsertFileLink(*pLink, sfx2::SvBaseLinkObjectType::ClientFile, aURI); + if ( mpLinkManager->GetLinks().size() == 1 ) // the first one? + { + SfxBindings* pBindings = mrDoc.GetViewBindings(); + if (pBindings) + pBindings->Invalidate( SID_LINKS ); // Link-Manager enabled + } + + //if the document was just loaded, but the ScDdeLink entry was missing, then + //don't update this link until the links are updated in response to the users + //decision + if (!mrDoc.HasLinkFormulaNeedingCheck()) + { + pLink->Update(); + } + + if (pMyFormulaCell) + { + // StartListening after the Update to avoid circular references + pMyFormulaCell->StartListening(*pLink); + } + } + else + { + if (pMyFormulaCell) + pMyFormulaCell->StartListening(*pLink); + } + + // If a new Error from Reschedule appears when the link is executed then reset the errorflag + if (pMyFormulaCell && pMyFormulaCell->GetRawError() != FormulaError::NONE && !bWasError) + pMyFormulaCell->SetErrCode(FormulaError::NONE); + + // check the value + if (pLink->HasResult()) + PushString(pLink->GetResult()); + else if (mrDoc.HasLinkFormulaNeedingCheck()) + { + // If this formula cell is recalculated just after load and the + // expression is exactly WEBSERVICE("literal_URI") (i.e. no other + // calculation involved, not even a cell reference) and a cached + // result is set as hybrid string then use that as result value to + // prevent a #VALUE! result due to the "Automatic update of + // external links has been disabled." + // This will work only once, as the new formula cell result won't + // be a hybrid anymore. + /* TODO: the FormulaError::LinkFormulaNeedingCheck could be used as + * a signal for the formula cell to keep the hybrid string as + * result of the overall formula *iff* no higher prioritized + * ScRecalcMode than ONLOAD_LENIENT is present in the entire + * document (i.e. the formula result could not be influenced by an + * ONLOAD_MUST or ALWAYS recalc, necessary as we don't track + * interim results of subexpressions that could be compared), which + * also means to track setting ScRecalcMode somehow... note this is + * just a vague idea so far and might or might not work. */ + if (pMyFormulaCell && pMyFormulaCell->HasHybridStringResult()) + { + if (pMyFormulaCell->GetCode()->GetCodeLen() == 2) + { + formula::FormulaToken const * const * pRPN = pMyFormulaCell->GetCode()->GetCode(); + if (pRPN[0]->GetType() == formula::svString && pRPN[1]->GetOpCode() == ocWebservice) + PushString( pMyFormulaCell->GetResultString()); + else + PushError(FormulaError::LinkFormulaNeedingCheck); + } + else + PushError(FormulaError::LinkFormulaNeedingCheck); + } + else + PushError(FormulaError::LinkFormulaNeedingCheck); + } + else + PushError(FormulaError::NoValue); + + mrDoc.EnableIdle(bOldEnabled); + mpLinkManager->CloseCachedComps(); +} + +/** + Returns a string in which all non-alphanumeric characters except stroke and + underscore (-_) have been replaced with a percent (%) sign + followed by hex digits. + It is encoded the same way that the posted data from a WWW form is encoded, + that is the same way as in application/x-www-form-urlencoded media type and + as per RFC 3986. + + @see fdo#76870 +*/ +void ScInterpreter::ScEncodeURL() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 1 ) ) + return; + + OUString aStr = GetString().getString(); + if ( aStr.isEmpty() ) + { + PushError( FormulaError::NoValue ); + return; + } + + OString aUtf8Str( aStr.toUtf8()); + const sal_Int32 nLen = aUtf8Str.getLength(); + OStringBuffer aUrlBuf( nLen ); + for ( int i = 0; i < nLen; i++ ) + { + char c = aUtf8Str[ i ]; + if ( rtl::isAsciiAlphanumeric( static_cast<unsigned char>( c ) ) || c == '-' || c == '_' ) + aUrlBuf.append( c ); + else + { + aUrlBuf.append( '%' ); + OString convertedChar = OString::number( static_cast<unsigned char>( c ), 16 ).toAsciiUpperCase(); + // RFC 3986 indicates: + // "A percent-encoded octet is encoded as a character triplet, + // consisting of the percent character "%" followed by the two hexadecimal digits + // representing that octet's numeric value" + if (convertedChar.getLength() == 1) + aUrlBuf.append("0"); + aUrlBuf.append(convertedChar); + } + } + PushString( OUString::fromUtf8( aUrlBuf ) ); +} + +void ScInterpreter::ScDebugVar() +{ + // This is to be used by developers only! Never document this for end + // users. This is a convenient way to extract arbitrary internal state to + // a cell for easier debugging. + + if (!officecfg::Office::Common::Misc::ExperimentalMode::get()) + { + PushError(FormulaError::NoName); + return; + } + + if (!MustHaveParamCount(GetByte(), 1)) + return; + + rtl_uString* p = GetString().getDataIgnoreCase(); + if (!p) + { + PushIllegalParameter(); + return; + } + + OUString aStrUpper(p); + + if (aStrUpper == "PIVOTCOUNT") + { + // Set the number of pivot tables in the document. + + double fVal = 0.0; + if (mrDoc.HasPivotTable()) + { + const ScDPCollection* pDPs = mrDoc.GetDPCollection(); + fVal = pDPs->GetCount(); + } + PushDouble(fVal); + } + else if (aStrUpper == "DATASTREAM_IMPORT") + PushDouble( sc::datastream_get_time( sc::DebugTime::Import ) ); + else if (aStrUpper == "DATASTREAM_RECALC") + PushDouble( sc::datastream_get_time( sc::DebugTime::Recalc ) ); + else if (aStrUpper == "DATASTREAM_RENDER") + PushDouble( sc::datastream_get_time( sc::DebugTime::Render ) ); + else + PushIllegalParameter(); +} + +void ScInterpreter::ScErf() +{ + sal_uInt8 nParamCount = GetByte(); + if (MustHaveParamCount( nParamCount, 1 ) ) + PushDouble( std::erf( GetDouble() ) ); +} + +void ScInterpreter::ScErfc() +{ + sal_uInt8 nParamCount = GetByte(); + if (MustHaveParamCount( nParamCount, 1 ) ) + PushDouble( std::erfc( GetDouble() ) ); +} + +void ScInterpreter::ScColor() +{ + sal_uInt8 nParamCount = GetByte(); + if(!MustHaveParamCount(nParamCount, 3, 4)) + return; + + double nAlpha = 0; + if(nParamCount == 4) + nAlpha = rtl::math::approxFloor(GetDouble()); + + if(nAlpha < 0 || nAlpha > 255) + { + PushIllegalArgument(); + return; + } + + double nBlue = rtl::math::approxFloor(GetDouble()); + + if(nBlue < 0 || nBlue > 255) + { + PushIllegalArgument(); + return; + } + + double nGreen = rtl::math::approxFloor(GetDouble()); + + if(nGreen < 0 || nGreen > 255) + { + PushIllegalArgument(); + return; + } + + double nRed = rtl::math::approxFloor(GetDouble()); + + if(nRed < 0 || nRed > 255) + { + PushIllegalArgument(); + return; + } + + double nVal = 256*256*256*nAlpha + 256*256*nRed + 256*nGreen + nBlue; + PushDouble(nVal); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/interpr8.cxx b/sc/source/core/tool/interpr8.cxx new file mode 100644 index 0000000000..8d3f7c25f4 --- /dev/null +++ b/sc/source/core/tool/interpr8.cxx @@ -0,0 +1,2008 @@ +/* -*- 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/. + * + */ + +#include <interpre.hxx> +#include <cellvalue.hxx> +#include <scmatrix.hxx> +#include <comphelper/random.hxx> +#include <formula/token.hxx> +#include <sal/log.hxx> +#include <svl/numformat.hxx> + +#include <cmath> +#include <memory> +#include <vector> + +using namespace formula; + +namespace { + +struct DataPoint +{ + double X, Y; + + DataPoint( double rX, double rY ) : X( rX ), Y( rY ) {}; +}; + +} + +static bool lcl_SortByX( const DataPoint &lhs, const DataPoint &rhs ) { return lhs.X < rhs.X; } + +/* + * ScETSForecastCalculation + * + * Class is set up to be used with Calc's FORECAST.ETS + * functions and with chart extrapolations (not yet implemented). + * + * Triple Exponential Smoothing (Holt-Winters method) + * + * Forecasting of a linear change in data over time (y=a+b*x) with + * superimposed absolute or relative seasonal deviations, using additive + * respectively multiplicative Holt-Winters method. + * + * Initialisation and forecasting calculations are based on + * Engineering Statistics Handbook, 6.4.3.5 Triple Exponential Smoothing + * see "http://www.itl.nist.gov/div898/handbook/pmc/section4/pmc435.htm" + * Further to the above is that initial calculation of Seasonal effect + * is corrected for trend. + * + * Prediction Interval calculations are based on + * Yar & Chatfield, Prediction Intervals for the Holt-Winters forecasting + * procedure, International Journal of Forecasting, 1990, Vol.6, pp127-137 + * The calculation here is a simplified numerical approximation of the above, + * using random distributions. + * + * Double Exponential Smoothing (Holt-Winters method) + * + * Forecasting of a linear change in data over time (y=a+b*x), using + * the Holt-Winters method. + * + * Initialisation and forecasting calculations are based on + * Engineering Statistics Handbook, 6.4.3.3 Double Exponential Smoothing + * see "http://www.itl.nist.gov/div898/handbook/pmc/section4/pmc433.htm" + * + * Prediction Interval calculations are based on + * Statistical Methods for Forecasting, Bovas & Ledolter, 2009, 3.8 Prediction + * Intervals for Future Values + * + */ + +namespace { + +class ScETSForecastCalculation +{ +private: + SvNumberFormatter* mpFormatter; + std::vector< DataPoint > maRange; // data (X, Y) + std::unique_ptr<double[]> mpBase; // calculated base value array + std::unique_ptr<double[]> mpTrend; // calculated trend factor array + std::unique_ptr<double[]> mpPerIdx; // calculated periodical deviation array, not used with eds + std::unique_ptr<double[]> mpForecast; // forecasted value array + SCSIZE mnSmplInPrd; // samples per period + double mfStepSize; // increment of X in maRange + double mfAlpha, mfBeta, mfGamma; // constants to minimize the RMSE in the ES-equations + SCSIZE mnCount; // No of data points + bool mbInitialised; + int mnMonthDay; // n-month X-interval, value is day of month + // accuracy indicators + double mfMAE; // mean absolute error + double mfMASE; // mean absolute scaled error + double mfMSE; // mean squared error (variation) + double mfRMSE; // root mean squared error (standard deviation) + double mfSMAPE; // symmetric mean absolute error + FormulaError mnErrorValue; + bool bAdditive; // true: additive method, false: multiplicative method + bool bEDS; // true: EDS, false: ETS + + // constants used in determining best fit for alpha, beta, gamma + static constexpr double cfMinABCResolution = 0.001; // minimum change of alpha, beta, gamma + static const SCSIZE cnScenarios = 1000; // No. of scenarios to calculate for PI calculations + + bool initData(); + void prefillBaseData(); + bool prefillTrendData(); + bool prefillPerIdx(); + void initCalc(); + void refill(); + SCSIZE CalcPeriodLen(); + void CalcAlphaBetaGamma(); + void CalcBetaGamma(); + void CalcGamma(); + void calcAccuracyIndicators(); + void GetForecast( double fTarget, double& rForecast ); + double RandDev(); + double convertXtoMonths( double x ); + +public: + ScETSForecastCalculation( SCSIZE nSize, SvNumberFormatter* pFormatter ); + + bool PreprocessDataRange( const ScMatrixRef& rMatX, const ScMatrixRef& rMatY, int nSmplInPrd, + bool bDataCompletion, int nAggregation, const ScMatrixRef& rTMat, + ScETSType eETSType ); + FormulaError GetError() const { return mnErrorValue; }; + void GetForecastRange( const ScMatrixRef& rTMat, const ScMatrixRef& rFcMat ); + void GetStatisticValue( const ScMatrixRef& rTypeMat, const ScMatrixRef& rStatMat ); + void GetSamplesInPeriod( double& rVal ); + void GetEDSPredictionIntervals( const ScMatrixRef& rTMat, const ScMatrixRef& rPIMat, double fPILevel ); + void GetETSPredictionIntervals( const ScMatrixRef& rTMat, const ScMatrixRef& rPIMat, double fPILevel ); +}; + +} + +ScETSForecastCalculation::ScETSForecastCalculation( SCSIZE nSize, SvNumberFormatter* pFormatter ) + : mpFormatter(pFormatter) + , mnSmplInPrd(0) + , mfStepSize(0.0) + , mfAlpha(0.0) + , mfBeta(0.0) + , mfGamma(0.0) + , mnCount(nSize) + , mbInitialised(false) + , mnMonthDay(0) + , mfMAE(0.0) + , mfMASE(0.0) + , mfMSE(0.0) + , mfRMSE(0.0) + , mfSMAPE(0.0) + , mnErrorValue(FormulaError::NONE) + , bAdditive(false) + , bEDS(false) +{ + maRange.reserve( mnCount ); +} + +bool ScETSForecastCalculation::PreprocessDataRange( const ScMatrixRef& rMatX, const ScMatrixRef& rMatY, int nSmplInPrd, + bool bDataCompletion, int nAggregation, const ScMatrixRef& rTMat, + ScETSType eETSType ) +{ + bEDS = ( nSmplInPrd == 0 ); + bAdditive = ( eETSType == etsAdd || eETSType == etsPIAdd || eETSType == etsStatAdd ); + + // maRange needs to be sorted by X + for ( SCSIZE i = 0; i < mnCount; i++ ) + maRange.emplace_back( rMatX->GetDouble( i ), rMatY->GetDouble( i ) ); + sort( maRange.begin(), maRange.end(), lcl_SortByX ); + + if ( rTMat ) + { + if ( eETSType != etsPIAdd && eETSType != etsPIMult ) + { + if ( rTMat->GetDouble( 0 ) < maRange[ 0 ].X ) + { + // target cannot be less than start of X-range + mnErrorValue = FormulaError::IllegalFPOperation; + return false; + } + } + else + { + if ( rTMat->GetDouble( 0 ) < maRange[ mnCount - 1 ].X ) + { + // target cannot be before end of X-range + mnErrorValue = FormulaError::IllegalFPOperation; + return false; + } + } + } + + // Month intervals don't have exact stepsize, so first + // detect if month interval is used. + // Method: assume there is an month interval and verify. + // If month interval is used, replace maRange.X with month values + // for ease of calculations. + Date aNullDate = mpFormatter->GetNullDate(); + Date aDate = aNullDate + static_cast< sal_Int32 >( maRange[ 0 ].X ); + mnMonthDay = aDate.GetDay(); + for ( SCSIZE i = 1; i < mnCount && mnMonthDay; i++ ) + { + Date aDate1 = aNullDate + static_cast< sal_Int32 >( maRange[ i ].X ); + if ( aDate != aDate1 ) + { + if ( aDate1.GetDay() != mnMonthDay ) + mnMonthDay = 0; + } + } + + mfStepSize = ::std::numeric_limits<double>::max(); + if ( mnMonthDay ) + { + for ( SCSIZE i = 0; i < mnCount; i++ ) + { + aDate = aNullDate + static_cast< sal_Int32 >( maRange[ i ].X ); + maRange[ i ].X = aDate.GetYear() * 12 + aDate.GetMonth(); + } + } + for ( SCSIZE i = 1; i < mnCount; i++ ) + { + double fStep = maRange[ i ].X - maRange[ i - 1 ].X; + if ( fStep == 0.0 ) + { + if ( nAggregation == 0 ) + { + // identical X-values are not allowed + mnErrorValue = FormulaError::NoValue; + return false; + } + double fTmp = maRange[ i - 1 ].Y; + SCSIZE nCounter = 1; + switch ( nAggregation ) + { + case 1 : // AVERAGE (default) + while ( i < mnCount && maRange[ i ].X == maRange[ i - 1 ].X ) + { + maRange.erase( maRange.begin() + i ); + --mnCount; + } + break; + case 7 : // SUM + while ( i < mnCount && maRange[ i ].X == maRange[ i - 1 ].X ) + { + fTmp += maRange[ i ].Y; + maRange.erase( maRange.begin() + i ); + --mnCount; + } + maRange[ i - 1 ].Y = fTmp; + break; + + case 2 : // COUNT + case 3 : // COUNTA (same as COUNT as there are no non-numeric Y-values) + while ( i < mnCount && maRange[ i ].X == maRange[ i - 1 ].X ) + { + nCounter++; + maRange.erase( maRange.begin() + i ); + --mnCount; + } + maRange[ i - 1 ].Y = nCounter; + break; + + case 4 : // MAX + while ( i < mnCount && maRange[ i ].X == maRange[ i - 1 ].X ) + { + if ( maRange[ i ].Y > fTmp ) + fTmp = maRange[ i ].Y; + maRange.erase( maRange.begin() + i ); + --mnCount; + } + maRange[ i - 1 ].Y = fTmp; + break; + + case 5 : // MEDIAN + { + std::vector< double > aTmp { maRange[ i - 1 ].Y }; + while ( i < mnCount && maRange[ i ].X == maRange[ i - 1 ].X ) + { + aTmp.push_back( maRange[ i ].Y ); + nCounter++; + maRange.erase( maRange.begin() + i ); + --mnCount; + } + sort( aTmp.begin(), aTmp.end() ); + + if ( nCounter % 2 ) + maRange[ i - 1 ].Y = aTmp[ nCounter / 2 ]; + else + maRange[ i - 1 ].Y = ( aTmp[ nCounter / 2 ] + aTmp[ nCounter / 2 - 1 ] ) / 2.0; + } + break; + + case 6 : // MIN + while ( i < mnCount && maRange[ i ].X == maRange[ i - 1 ].X ) + { + if ( maRange[ i ].Y < fTmp ) + fTmp = maRange[ i ].Y; + maRange.erase( maRange.begin() + i ); + --mnCount; + } + maRange[ i - 1 ].Y = fTmp; + break; + } + if ( i < mnCount - 1 ) + fStep = maRange[ i ].X - maRange[ i - 1 ].X; + else + fStep = mfStepSize; + } + if ( fStep > 0 && fStep < mfStepSize ) + mfStepSize = fStep; + } + + // step must be constant (or gap multiple of step) + bool bHasGap = false; + for ( SCSIZE i = 1; i < mnCount && !bHasGap; i++ ) + { + double fStep = maRange[ i ].X - maRange[ i - 1 ].X; + + if ( fStep != mfStepSize ) + { + if ( fmod( fStep, mfStepSize ) != 0.0 ) + { + // step not constant nor multiple of mfStepSize in case of gaps + mnErrorValue = FormulaError::NoValue; + return false; + } + bHasGap = true; + } + } + + // fill gaps with values depending on bDataCompletion + if ( bHasGap ) + { + SCSIZE nMissingXCount = 0; + double fOriginalCount = static_cast< double >( mnCount ); + if ( mnMonthDay ) + aDate = aNullDate + static_cast< sal_Int32 >( maRange[ 0 ].X ); + for ( SCSIZE i = 1; i < mnCount; i++ ) + { + double fDist; + if ( mnMonthDay ) + { + Date aDate1 = aNullDate + static_cast< sal_Int32 >( maRange[ i ].X ); + fDist = 12 * ( aDate1.GetYear() - aDate.GetYear() ) + + ( aDate1.GetMonth() - aDate.GetMonth() ); + aDate = aDate1; + } + else + fDist = maRange[ i ].X - maRange[ i - 1 ].X; + if ( fDist > mfStepSize ) + { + // gap, insert missing data points + double fYGap = ( maRange[ i ].Y + maRange[ i - 1 ].Y ) / 2.0; + for ( KahanSum fXGap = maRange[ i - 1].X + mfStepSize; fXGap < maRange[ i ].X; fXGap += mfStepSize ) + { + maRange.insert( maRange.begin() + i, DataPoint( fXGap.get(), ( bDataCompletion ? fYGap : 0.0 ) ) ); + i++; + mnCount++; + nMissingXCount++; + if ( static_cast< double >( nMissingXCount ) / fOriginalCount > 0.3 ) + { + // maximum of 30% missing points exceeded + mnErrorValue = FormulaError::NoValue; + return false; + } + } + } + } + } + + if ( nSmplInPrd != 1 ) + mnSmplInPrd = nSmplInPrd; + else + { + mnSmplInPrd = CalcPeriodLen(); + if ( mnSmplInPrd == 1 ) + bEDS = true; // period length 1 means no periodic data: EDS suffices + } + + if ( !initData() ) + return false; // note: mnErrorValue is set in called function(s) + + return true; +} + +bool ScETSForecastCalculation::initData( ) +{ + // give various vectors size and initial value + mpBase.reset( new double[ mnCount ] ); + mpTrend.reset( new double[ mnCount ] ); + if ( !bEDS ) + mpPerIdx.reset( new double[ mnCount ] ); + mpForecast.reset( new double[ mnCount ] ); + mpForecast[ 0 ] = maRange[ 0 ].Y; + + if ( prefillTrendData() ) + { + if ( prefillPerIdx() ) + { + prefillBaseData(); + return true; + } + } + return false; +} + +bool ScETSForecastCalculation::prefillTrendData() +{ + if ( bEDS ) + mpTrend[ 0 ] = ( maRange[ mnCount - 1 ].Y - maRange[ 0 ].Y ) / static_cast< double >( mnCount - 1 ); + else + { + // we need at least 2 periods in the data range + if ( mnCount < 2 * mnSmplInPrd ) + { + mnErrorValue = FormulaError::NoValue; + return false; + } + + KahanSum fSum = 0.0; + for ( SCSIZE i = 0; i < mnSmplInPrd; i++ ) + { + fSum += maRange[ i + mnSmplInPrd ].Y; + fSum -= maRange[ i ].Y; + } + double fTrend = fSum.get() / static_cast< double >( mnSmplInPrd * mnSmplInPrd ); + + mpTrend[ 0 ] = fTrend; + } + + return true; +} + +bool ScETSForecastCalculation::prefillPerIdx() +{ + if ( !bEDS ) + { + // use as many complete periods as available + if ( mnSmplInPrd == 0 ) + { + // should never happen; if mnSmplInPrd equals 0, bEDS is true + mnErrorValue = FormulaError::UnknownState; + return false; + } + SCSIZE nPeriods = mnCount / mnSmplInPrd; + std::vector< KahanSum > aPeriodAverage( nPeriods, 0.0 ); + for ( SCSIZE i = 0; i < nPeriods ; i++ ) + { + for ( SCSIZE j = 0; j < mnSmplInPrd; j++ ) + aPeriodAverage[ i ] += maRange[ i * mnSmplInPrd + j ].Y; + aPeriodAverage[ i ] /= static_cast< double >( mnSmplInPrd ); + if ( aPeriodAverage[ i ] == 0.0 ) + { + SAL_WARN( "sc.core", "prefillPerIdx(), average of 0 will cause divide by zero error, quitting calculation" ); + mnErrorValue = FormulaError::DivisionByZero; + return false; + } + } + + for ( SCSIZE j = 0; j < mnSmplInPrd; j++ ) + { + KahanSum fI = 0.0; + for ( SCSIZE i = 0; i < nPeriods ; i++ ) + { + // adjust average value for position within period + if ( bAdditive ) + fI += maRange[ i * mnSmplInPrd + j ].Y - + ( aPeriodAverage[ i ].get() + ( static_cast< double >( j ) - 0.5 * ( mnSmplInPrd - 1 ) ) * + mpTrend[ 0 ] ); + else + fI += maRange[ i * mnSmplInPrd + j ].Y / + ( aPeriodAverage[ i ].get() + ( static_cast< double >( j ) - 0.5 * ( mnSmplInPrd - 1 ) ) * + mpTrend[ 0 ] ); + } + mpPerIdx[ j ] = fI.get() / nPeriods; + } + if (mnSmplInPrd < mnCount) + mpPerIdx[mnSmplInPrd] = 0.0; + } + return true; +} + +void ScETSForecastCalculation::prefillBaseData() +{ + if ( bEDS ) + mpBase[ 0 ] = maRange[ 0 ].Y; + else + mpBase[ 0 ] = maRange[ 0 ].Y / mpPerIdx[ 0 ]; +} + +void ScETSForecastCalculation::initCalc() +{ + if ( !mbInitialised ) + { + CalcAlphaBetaGamma(); + + mbInitialised = true; + calcAccuracyIndicators(); + } +} + +void ScETSForecastCalculation::calcAccuracyIndicators() +{ + KahanSum fSumAbsErr = 0.0; + KahanSum fSumDivisor = 0.0; + KahanSum fSumErrSq = 0.0; + KahanSum fSumAbsPercErr = 0.0; + + for ( SCSIZE i = 1; i < mnCount; i++ ) + { + double fError = mpForecast[ i ] - maRange[ i ].Y; + fSumAbsErr += fabs( fError ); + fSumErrSq += fError * fError; + fSumAbsPercErr += fabs( fError ) / ( fabs( mpForecast[ i ] ) + fabs( maRange[ i ].Y ) ); + } + + for ( SCSIZE i = 2; i < mnCount; i++ ) + fSumDivisor += fabs( maRange[ i ].Y - maRange[ i - 1 ].Y ); + + int nCalcCount = mnCount - 1; + mfMAE = fSumAbsErr.get() / nCalcCount; + mfMASE = fSumAbsErr.get() / ( nCalcCount * fSumDivisor.get() / ( nCalcCount - 1 ) ); + mfMSE = fSumErrSq.get() / nCalcCount; + mfRMSE = sqrt( mfMSE ); + mfSMAPE = fSumAbsPercErr.get() * 2.0 / nCalcCount; +} + +/* + * CalcPeriodLen() calculates the most likely length of a period. + * + * Method used: for all possible values (between mnCount/2 and 2) compare for + * each (sample-previous sample) with next period and calculate mean error. + * Use as much samples as possible for each period length and the most recent samples + * Return the period length with the lowest mean error. + */ +SCSIZE ScETSForecastCalculation::CalcPeriodLen() +{ + SCSIZE nBestVal = mnCount; + double fBestME = ::std::numeric_limits<double>::max(); + + for ( SCSIZE nPeriodLen = mnCount / 2; nPeriodLen >= 1; nPeriodLen-- ) + { + KahanSum fMeanError = 0.0; + SCSIZE nPeriods = mnCount / nPeriodLen; + SCSIZE nStart = mnCount - ( nPeriods * nPeriodLen ) + 1; + for ( SCSIZE i = nStart; i < ( mnCount - nPeriodLen ); i++ ) + { + fMeanError += fabs( ( maRange[ i ].Y - maRange[ i - 1 ].Y ) - + ( maRange[ nPeriodLen + i ].Y - maRange[ nPeriodLen + i - 1 ].Y ) ); + } + double fMeanErrorGet = fMeanError.get(); + fMeanErrorGet /= static_cast< double >( ( nPeriods - 1 ) * nPeriodLen - 1 ); + + if ( fMeanErrorGet <= fBestME || fMeanErrorGet == 0.0 ) + { + nBestVal = nPeriodLen; + fBestME = fMeanErrorGet; + } + } + return nBestVal; +} + +void ScETSForecastCalculation::CalcAlphaBetaGamma() +{ + double f0 = 0.0; + mfAlpha = f0; + if ( bEDS ) + { + mfBeta = 0.0; // beta is not used with EDS + CalcGamma(); + } + else + CalcBetaGamma(); + refill(); + double fE0 = mfMSE; + + double f2 = 1.0; + mfAlpha = f2; + if ( bEDS ) + CalcGamma(); + else + CalcBetaGamma(); + refill(); + double fE2 = mfMSE; + + double f1 = 0.5; + mfAlpha = f1; + if ( bEDS ) + CalcGamma(); + else + CalcBetaGamma(); + refill(); + + if ( fE0 == mfMSE && mfMSE == fE2 ) + { + mfAlpha = 0; + if ( bEDS ) + CalcGamma(); + else + CalcBetaGamma(); + refill(); + return; + } + while ( ( f2 - f1 ) > cfMinABCResolution ) + { + if ( fE2 > fE0 ) + { + f2 = f1; + fE2 = mfMSE; + f1 = ( f0 + f1 ) / 2; + } + else + { + f0 = f1; + fE0 = mfMSE; + f1 = ( f1 + f2 ) / 2; + } + mfAlpha = f1; + if ( bEDS ) + CalcGamma(); + else + CalcBetaGamma(); + refill(); + } + if ( fE2 > fE0 ) + { + if ( fE0 < mfMSE ) + { + mfAlpha = f0; + if ( bEDS ) + CalcGamma(); + else + CalcBetaGamma(); + refill(); + } + } + else + { + if ( fE2 < mfMSE ) + { + mfAlpha = f2; + if ( bEDS ) + CalcGamma(); + else + CalcBetaGamma(); + refill(); + } + } + calcAccuracyIndicators(); +} + +void ScETSForecastCalculation::CalcBetaGamma() +{ + double f0 = 0.0; + mfBeta = f0; + CalcGamma(); + refill(); + double fE0 = mfMSE; + + double f2 = 1.0; + mfBeta = f2; + CalcGamma(); + refill(); + double fE2 = mfMSE; + + double f1 = 0.5; + mfBeta = f1; + CalcGamma(); + refill(); + + if ( fE0 == mfMSE && mfMSE == fE2 ) + { + mfBeta = 0; + CalcGamma(); + refill(); + return; + } + while ( ( f2 - f1 ) > cfMinABCResolution ) + { + if ( fE2 > fE0 ) + { + f2 = f1; + fE2 = mfMSE; + f1 = ( f0 + f1 ) / 2; + } + else + { + f0 = f1; + fE0 = mfMSE; + f1 = ( f1 + f2 ) / 2; + } + mfBeta = f1; + CalcGamma(); + refill(); + } + if ( fE2 > fE0 ) + { + if ( fE0 < mfMSE ) + { + mfBeta = f0; + CalcGamma(); + refill(); + } + } + else + { + if ( fE2 < mfMSE ) + { + mfBeta = f2; + CalcGamma(); + refill(); + } + } +} + +void ScETSForecastCalculation::CalcGamma() +{ + double f0 = 0.0; + mfGamma = f0; + refill(); + double fE0 = mfMSE; + + double f2 = 1.0; + mfGamma = f2; + refill(); + double fE2 = mfMSE; + + double f1 = 0.5; + mfGamma = f1; + refill(); + + if ( fE0 == mfMSE && mfMSE == fE2 ) + { + mfGamma = 0; + refill(); + return; + } + while ( ( f2 - f1 ) > cfMinABCResolution ) + { + if ( fE2 > fE0 ) + { + f2 = f1; + fE2 = mfMSE; + f1 = ( f0 + f1 ) / 2; + } + else + { + f0 = f1; + fE0 = mfMSE; + f1 = ( f1 + f2 ) / 2; + } + mfGamma = f1; + refill(); + } + if ( fE2 > fE0 ) + { + if ( fE0 < mfMSE ) + { + mfGamma = f0; + refill(); + } + } + else + { + if ( fE2 < mfMSE ) + { + mfGamma = f2; + refill(); + } + } +} + +void ScETSForecastCalculation::refill() +{ + // refill mpBase, mpTrend, mpPerIdx and mpForecast with values + // using the calculated mfAlpha, (mfBeta), mfGamma + // forecast 1 step ahead + for ( SCSIZE i = 1; i < mnCount; i++ ) + { + if ( bEDS ) + { + mpBase[ i ] = mfAlpha * maRange[ i ].Y + + ( 1 - mfAlpha ) * ( mpBase[ i - 1 ] + mpTrend[ i - 1 ] ); + mpTrend[ i ] = mfGamma * ( mpBase[ i ] - mpBase[ i - 1 ] ) + + ( 1 - mfGamma ) * mpTrend[ i - 1 ]; + mpForecast[ i ] = mpBase[ i - 1 ] + mpTrend[ i - 1 ]; + } + else + { + SCSIZE nIdx; + if ( bAdditive ) + { + nIdx = ( i > mnSmplInPrd ? i - mnSmplInPrd : i ); + mpBase[ i ] = mfAlpha * ( maRange[ i ].Y - mpPerIdx[ nIdx ] ) + + ( 1 - mfAlpha ) * ( mpBase[ i - 1 ] + mpTrend[ i - 1 ] ); + mpPerIdx[ i ] = mfBeta * ( maRange[ i ].Y - mpBase[ i ] ) + + ( 1 - mfBeta ) * mpPerIdx[ nIdx ]; + } + else + { + nIdx = ( i >= mnSmplInPrd ? i - mnSmplInPrd : i ); + mpBase[ i ] = mfAlpha * ( maRange[ i ].Y / mpPerIdx[ nIdx ] ) + + ( 1 - mfAlpha ) * ( mpBase[ i - 1 ] + mpTrend[ i - 1 ] ); + mpPerIdx[ i ] = mfBeta * ( maRange[ i ].Y / mpBase[ i ] ) + + ( 1 - mfBeta ) * mpPerIdx[ nIdx ]; + } + mpTrend[ i ] = mfGamma * ( mpBase[ i ] - mpBase[ i - 1 ] ) + + ( 1 - mfGamma ) * mpTrend[ i - 1 ]; + + if ( bAdditive ) + mpForecast[ i ] = mpBase[ i - 1 ] + mpTrend[ i - 1 ] + mpPerIdx[ nIdx ]; + else + mpForecast[ i ] = ( mpBase[ i - 1 ] + mpTrend[ i - 1 ] ) * mpPerIdx[ nIdx ]; + } + } + calcAccuracyIndicators(); +} + +double ScETSForecastCalculation::convertXtoMonths( double x ) +{ + Date aDate = mpFormatter->GetNullDate() + static_cast< sal_Int32 >( x ); + int nYear = aDate.GetYear(); + int nMonth = aDate.GetMonth(); + double fMonthLength; + switch ( nMonth ) + { + case 1 : + case 3 : + case 5 : + case 7 : + case 8 : + case 10 : + case 12 : + fMonthLength = 31.0; + break; + case 2 : + fMonthLength = ( aDate.IsLeapYear() ? 29.0 : 28.0 ); + break; + default : + fMonthLength = 30.0; + } + return ( 12.0 * nYear + nMonth + ( aDate.GetDay() - mnMonthDay ) / fMonthLength ); +} + +void ScETSForecastCalculation::GetForecast( double fTarget, double& rForecast ) +{ + initCalc(); + + if ( fTarget <= maRange[ mnCount - 1 ].X ) + { + SCSIZE n = ( fTarget - maRange[ 0 ].X ) / mfStepSize; + double fInterpolate = fmod( fTarget - maRange[ 0 ].X, mfStepSize ); + rForecast = maRange[ n ].Y; + + if ( fInterpolate >= cfMinABCResolution ) + { + double fInterpolateFactor = fInterpolate / mfStepSize; + double fFc_1 = mpForecast[ n + 1 ]; + rForecast = rForecast + fInterpolateFactor * ( fFc_1 - rForecast ); + } + } + else + { + SCSIZE n = ( fTarget - maRange[ mnCount - 1 ].X ) / mfStepSize; + double fInterpolate = fmod( fTarget - maRange[ mnCount - 1 ].X, mfStepSize ); + + if ( bEDS ) + rForecast = mpBase[ mnCount - 1 ] + n * mpTrend[ mnCount - 1 ]; + else if ( bAdditive ) + rForecast = mpBase[ mnCount - 1 ] + n * mpTrend[ mnCount - 1 ] + + mpPerIdx[ mnCount - 1 - mnSmplInPrd + ( n % mnSmplInPrd ) ]; + else + rForecast = ( mpBase[ mnCount - 1 ] + n * mpTrend[ mnCount - 1 ] ) * + mpPerIdx[ mnCount - 1 - mnSmplInPrd + ( n % mnSmplInPrd ) ]; + + if ( fInterpolate >= cfMinABCResolution ) + { + double fInterpolateFactor = fInterpolate / mfStepSize; + double fFc_1; + if ( bEDS ) + fFc_1 = mpBase[ mnCount - 1 ] + ( n + 1 ) * mpTrend[ mnCount - 1 ]; + else if ( bAdditive ) + fFc_1 = mpBase[ mnCount - 1 ] + ( n + 1 ) * mpTrend[ mnCount - 1 ] + + mpPerIdx[ mnCount - 1 - mnSmplInPrd + ( ( n + 1 ) % mnSmplInPrd ) ]; + else + fFc_1 = ( mpBase[ mnCount - 1 ] + ( n + 1 ) * mpTrend[ mnCount - 1 ] ) * + mpPerIdx[ mnCount - 1 - mnSmplInPrd + ( ( n + 1 ) % mnSmplInPrd ) ]; + rForecast = rForecast + fInterpolateFactor * ( fFc_1 - rForecast ); + } + } +} + +void ScETSForecastCalculation::GetForecastRange( const ScMatrixRef& rTMat, const ScMatrixRef& rFcMat ) +{ + SCSIZE nC, nR; + rTMat->GetDimensions( nC, nR ); + + for ( SCSIZE i = 0; i < nR; i++ ) + { + for ( SCSIZE j = 0; j < nC; j++ ) + { + double fTarget; + if ( mnMonthDay ) + fTarget = convertXtoMonths( rTMat->GetDouble( j, i ) ); + else + fTarget = rTMat->GetDouble( j, i ); + double fForecast; + GetForecast( fTarget, fForecast ); + rFcMat->PutDouble( fForecast, j, i ); + } + } +} + +void ScETSForecastCalculation::GetStatisticValue( const ScMatrixRef& rTypeMat, const ScMatrixRef& rStatMat ) +{ + initCalc(); + + SCSIZE nC, nR; + rTypeMat->GetDimensions( nC, nR ); + for ( SCSIZE i = 0; i < nR; i++ ) + { + for ( SCSIZE j = 0; j < nC; j++ ) + { + switch ( static_cast< int >( rTypeMat->GetDouble( j, i ) ) ) + { + case 1 : // alpha + rStatMat->PutDouble( mfAlpha, j, i ); + break; + case 2 : // gamma + rStatMat->PutDouble( mfGamma, j, i ); + break; + case 3 : // beta + rStatMat->PutDouble( mfBeta, j, i ); + break; + case 4 : // MASE + rStatMat->PutDouble( mfMASE, j, i ); + break; + case 5 : // SMAPE + rStatMat->PutDouble( mfSMAPE, j, i ); + break; + case 6 : // MAE + rStatMat->PutDouble( mfMAE, j, i ); + break; + case 7 : // RMSE + rStatMat->PutDouble( mfRMSE, j, i ); + break; + case 8 : // step size + rStatMat->PutDouble( mfStepSize, j, i ); + break; + case 9 : // samples in period + rStatMat->PutDouble( mnSmplInPrd, j, i ); + break; + } + } + } +} + +void ScETSForecastCalculation::GetSamplesInPeriod( double& rVal ) +{ + rVal = mnSmplInPrd; +} + +double ScETSForecastCalculation::RandDev() +{ + // return a random deviation given the standard deviation + return ( mfRMSE * ScInterpreter::gaussinv( + ::comphelper::rng::uniform_real_distribution( 0.5, 1.0 ) ) ); +} + +void ScETSForecastCalculation::GetETSPredictionIntervals( const ScMatrixRef& rTMat, const ScMatrixRef& rPIMat, double fPILevel ) +{ + initCalc(); + + SCSIZE nC, nR; + rTMat->GetDimensions( nC, nR ); + + // find maximum target value and calculate size of scenario-arrays + double fMaxTarget = rTMat->GetDouble( 0, 0 ); + for ( SCSIZE i = 0; i < nR; i++ ) + { + for ( SCSIZE j = 0; j < nC; j++ ) + { + if ( fMaxTarget < rTMat->GetDouble( j, i ) ) + fMaxTarget = rTMat->GetDouble( j, i ); + } + } + if ( mnMonthDay ) + fMaxTarget = convertXtoMonths( fMaxTarget ) - maRange[ mnCount - 1 ].X; + else + fMaxTarget -= maRange[ mnCount - 1 ].X; + SCSIZE nSize = fMaxTarget / mfStepSize; + if ( fmod( fMaxTarget, mfStepSize ) != 0.0 ) + nSize++; + + if (nSize == 0) + { + mnErrorValue = FormulaError::IllegalArgument; + return; + } + + std::unique_ptr< double[] > xScenRange( new double[nSize]); + std::unique_ptr< double[] > xScenBase( new double[nSize]); + std::unique_ptr< double[] > xScenTrend( new double[nSize]); + std::unique_ptr< double[] > xScenPerIdx( new double[nSize]); + std::vector< std::vector< double > > aPredictions( nSize, std::vector< double >( cnScenarios ) ); + + // fill scenarios + for ( SCSIZE k = 0; k < cnScenarios; k++ ) + { + // fill array with forecasts, with RandDev() added to xScenRange + if ( bAdditive ) + { + double nPIdx = !bEDS ? mpPerIdx[mnCount - mnSmplInPrd] : 0.0; + // calculation based on additive model + xScenRange[ 0 ] = mpBase[ mnCount - 1 ] + mpTrend[ mnCount - 1 ] + + nPIdx + + RandDev(); + aPredictions[ 0 ][ k ] = xScenRange[ 0 ]; + xScenBase[ 0 ] = mfAlpha * ( xScenRange[ 0 ] - nPIdx ) + + ( 1 - mfAlpha ) * ( mpBase[ mnCount - 1 ] + mpTrend[ mnCount - 1 ] ); + xScenTrend[ 0 ] = mfGamma * ( xScenBase[ 0 ] - mpBase[ mnCount - 1 ] ) + + ( 1 - mfGamma ) * mpTrend[ mnCount - 1 ]; + xScenPerIdx[ 0 ] = mfBeta * ( xScenRange[ 0 ] - xScenBase[ 0 ] ) + + ( 1 - mfBeta ) * nPIdx; + for ( SCSIZE i = 1; i < nSize; i++ ) + { + double fPerIdx; + if ( i < mnSmplInPrd ) + fPerIdx = mpPerIdx[ mnCount + i - mnSmplInPrd ]; + else + fPerIdx = xScenPerIdx[ i - mnSmplInPrd ]; + xScenRange[ i ] = xScenBase[ i - 1 ] + xScenTrend[ i - 1 ] + fPerIdx + RandDev(); + aPredictions[ i ][ k ] = xScenRange[ i ]; + xScenBase[ i ] = mfAlpha * ( xScenRange[ i ] - fPerIdx ) + + ( 1 - mfAlpha ) * ( xScenBase[ i - 1 ] + xScenTrend[ i - 1 ] ); + xScenTrend[ i ] = mfGamma * ( xScenBase[ i ] - xScenBase[ i - 1 ] ) + + ( 1 - mfGamma ) * xScenTrend[ i - 1 ]; + xScenPerIdx[ i ] = mfBeta * ( xScenRange[ i ] - xScenBase[ i ] ) + + ( 1 - mfBeta ) * fPerIdx; + } + } + else + { + // calculation based on multiplicative model + xScenRange[ 0 ] = ( mpBase[ mnCount - 1 ] + mpTrend[ mnCount - 1 ] ) * + mpPerIdx[ mnCount - mnSmplInPrd ] + + RandDev(); + aPredictions[ 0 ][ k ] = xScenRange[ 0 ]; + xScenBase[ 0 ] = mfAlpha * ( xScenRange[ 0 ] / mpPerIdx[ mnCount - mnSmplInPrd ] ) + + ( 1 - mfAlpha ) * ( mpBase[ mnCount - 1 ] + mpTrend[ mnCount - 1 ] ); + xScenTrend[ 0 ] = mfGamma * ( xScenBase[ 0 ] - mpBase[ mnCount - 1 ] ) + + ( 1 - mfGamma ) * mpTrend[ mnCount - 1 ]; + xScenPerIdx[ 0 ] = mfBeta * ( xScenRange[ 0 ] / xScenBase[ 0 ] ) + + ( 1 - mfBeta ) * mpPerIdx[ mnCount - mnSmplInPrd ]; + for ( SCSIZE i = 1; i < nSize; i++ ) + { + double fPerIdx; + if ( i < mnSmplInPrd ) + fPerIdx = mpPerIdx[ mnCount + i - mnSmplInPrd ]; + else + fPerIdx = xScenPerIdx[ i - mnSmplInPrd ]; + xScenRange[ i ] = ( xScenBase[ i - 1 ] + xScenTrend[ i - 1 ] ) * fPerIdx + RandDev(); + aPredictions[ i ][ k ] = xScenRange[ i ]; + xScenBase[ i ] = mfAlpha * ( xScenRange[ i ] / fPerIdx ) + + ( 1 - mfAlpha ) * ( xScenBase[ i - 1 ] + xScenTrend[ i - 1 ] ); + xScenTrend[ i ] = mfGamma * ( xScenBase[ i ] - xScenBase[ i - 1 ] ) + + ( 1 - mfGamma ) * xScenTrend[ i - 1 ]; + xScenPerIdx[ i ] = mfBeta * ( xScenRange[ i ] / xScenBase[ i ] ) + + ( 1 - mfBeta ) * fPerIdx; + } + } + } + + // create array of Percentile values; + std::unique_ptr< double[] > xPercentile( new double[nSize]); + for ( SCSIZE i = 0; i < nSize; i++ ) + { + xPercentile[ i ] = ScInterpreter::GetPercentile( aPredictions[ i ], ( 1 + fPILevel ) / 2 ) - + ScInterpreter::GetPercentile( aPredictions[ i ], 0.5 ); + } + + for ( SCSIZE i = 0; i < nR; i++ ) + { + for ( SCSIZE j = 0; j < nC; j++ ) + { + double fTarget; + if ( mnMonthDay ) + fTarget = convertXtoMonths( rTMat->GetDouble( j, i ) ) - maRange[ mnCount - 1 ].X; + else + fTarget = rTMat->GetDouble( j, i ) - maRange[ mnCount - 1 ].X; + SCSIZE nSteps = ( fTarget / mfStepSize ) - 1; + double fFactor = fmod( fTarget, mfStepSize ); + double fPI = xPercentile[ nSteps ]; + if ( fFactor != 0.0 ) + { + // interpolate + double fPI1 = xPercentile[ nSteps + 1 ]; + fPI = fPI + fFactor * ( fPI1 - fPI ); + } + rPIMat->PutDouble( fPI, j, i ); + } + } +} + + +void ScETSForecastCalculation::GetEDSPredictionIntervals( const ScMatrixRef& rTMat, const ScMatrixRef& rPIMat, double fPILevel ) +{ + initCalc(); + + SCSIZE nC, nR; + rTMat->GetDimensions( nC, nR ); + + // find maximum target value and calculate size of coefficient- array c + double fMaxTarget = rTMat->GetDouble( 0, 0 ); + for ( SCSIZE i = 0; i < nR; i++ ) + { + for ( SCSIZE j = 0; j < nC; j++ ) + { + if ( fMaxTarget < rTMat->GetDouble( j, i ) ) + fMaxTarget = rTMat->GetDouble( j, i ); + } + } + if ( mnMonthDay ) + fMaxTarget = convertXtoMonths( fMaxTarget ) - maRange[ mnCount - 1 ].X; + else + fMaxTarget -= maRange[ mnCount - 1 ].X; + SCSIZE nSize = fMaxTarget / mfStepSize; + if ( fmod( fMaxTarget, mfStepSize ) != 0.0 ) + nSize++; + + if (nSize == 0) + { + mnErrorValue = FormulaError::IllegalArgument; + return; + } + + double z = ScInterpreter::gaussinv( ( 1.0 + fPILevel ) / 2.0 ); + double o = 1 - fPILevel; + std::vector< double > c( nSize ); + for ( SCSIZE i = 0; i < nSize; i++ ) + { + c[ i ] = sqrt( 1 + ( fPILevel / pow( 1 + o, 3.0 ) ) * + ( ( 1 + 4 * o + 5 * o * o ) + + 2 * static_cast< double >( i ) * fPILevel * ( 1 + 3 * o ) + + 2 * static_cast< double >( i * i ) * fPILevel * fPILevel ) ); + } + + + for ( SCSIZE i = 0; i < nR; i++ ) + { + for ( SCSIZE j = 0; j < nC; j++ ) + { + double fTarget; + if ( mnMonthDay ) + fTarget = convertXtoMonths( rTMat->GetDouble( j, i ) ) - maRange[ mnCount - 1 ].X; + else + fTarget = rTMat->GetDouble( j, i ) - maRange[ mnCount - 1 ].X; + SCSIZE nSteps = ( fTarget / mfStepSize ) - 1; + double fFactor = fmod( fTarget, mfStepSize ); + double fPI = z * mfRMSE * c[ nSteps ] / c[ 0 ]; + if ( fFactor != 0.0 ) + { + // interpolate + double fPI1 = z * mfRMSE * c[ nSteps + 1 ] / c[ 0 ]; + fPI = fPI + fFactor * ( fPI1 - fPI ); + } + rPIMat->PutDouble( fPI, j, i ); + } + } +} + + +void ScInterpreter::ScForecast_Ets( ScETSType eETSType ) +{ + sal_uInt8 nParamCount = GetByte(); + switch ( eETSType ) + { + case etsAdd : + case etsMult : + case etsStatAdd : + case etsStatMult : + if ( !MustHaveParamCount( nParamCount, 3, 6 ) ) + return; + break; + case etsPIAdd : + case etsPIMult : + if ( !MustHaveParamCount( nParamCount, 3, 7 ) ) + { + return; + } + break; + case etsSeason : + if ( !MustHaveParamCount( nParamCount, 2, 4 ) ) + return; + break; + } + + int nAggregation; + if ( ( nParamCount == 6 && eETSType != etsPIAdd && eETSType != etsPIMult ) || + ( nParamCount == 4 && eETSType == etsSeason ) || + nParamCount == 7 ) + nAggregation = static_cast< int >( GetDoubleWithDefault( 1.0 ) ); + else + nAggregation = 1; + if ( nAggregation < 1 || nAggregation > 7 ) + { + PushIllegalArgument(); + return; + } + + bool bDataCompletion; + if ( ( nParamCount >= 5 && eETSType != etsPIAdd && eETSType != etsPIMult ) || + ( nParamCount >= 3 && eETSType == etsSeason ) || + ( nParamCount >= 6 && ( eETSType == etsPIAdd || eETSType == etsPIMult ) ) ) + { + int nTemp = static_cast< int >( GetDoubleWithDefault( 1.0 ) ); + if ( nTemp == 0 || nTemp == 1 ) + bDataCompletion = nTemp; + else + { + PushIllegalArgument(); + return; + } + } + else + bDataCompletion = true; + + int nSmplInPrd; + if ( ( ( nParamCount >= 4 && eETSType != etsPIAdd && eETSType != etsPIMult ) || + ( nParamCount >= 5 && ( eETSType == etsPIAdd || eETSType == etsPIMult ) ) ) && + eETSType != etsSeason ) + { + double fVal = GetDoubleWithDefault( 1.0 ); + if ( fmod( fVal, 1.0 ) != 0 || fVal < 0.0 ) + { + PushError( FormulaError::IllegalFPOperation ); + return; + } + nSmplInPrd = static_cast< int >( fVal ); + } + else + nSmplInPrd = 1; + + // required arguments + double fPILevel = 0.0; + if ( nParamCount < 3 && ( nParamCount != 2 || eETSType != etsSeason ) ) + { + PushParameterExpected(); + return; + } + + if ( eETSType == etsPIAdd || eETSType == etsPIMult ) + { + fPILevel = (nParamCount < 4 ? 0.95 : GetDoubleWithDefault( 0.95 )); + if ( fPILevel < 0 || fPILevel > 1 ) + { + PushIllegalArgument(); + return; + } + } + + ScMatrixRef pTypeMat; + if ( eETSType == etsStatAdd || eETSType == etsStatMult ) + { + pTypeMat = GetMatrix(); + SCSIZE nC, nR; + pTypeMat->GetDimensions( nC, nR ); + for ( SCSIZE i = 0; i < nR; i++ ) + { + for ( SCSIZE j = 0; j < nC; j++ ) + { + if ( static_cast< int >( pTypeMat->GetDouble( j, i ) ) < 1 || + static_cast< int >( pTypeMat->GetDouble( j, i ) ) > 9 ) + { + PushIllegalArgument(); + return; + } + } + } + } + + ScMatrixRef pMatX = GetMatrix(); + ScMatrixRef pMatY = GetMatrix(); + if ( !pMatX || !pMatY ) + { + PushIllegalParameter(); + return; + } + SCSIZE nCX, nCY; + SCSIZE nRX, nRY; + pMatX->GetDimensions( nCX, nRX ); + pMatY->GetDimensions( nCY, nRY ); + if ( nRX != nRY || nCX != nCY || + !pMatX->IsNumeric() || !pMatY->IsNumeric() ) + { + PushIllegalArgument(); + return; + } + + ScMatrixRef pTMat; + if ( eETSType != etsStatAdd && eETSType != etsStatMult && eETSType != etsSeason ) + { + pTMat = GetMatrix(); + if ( !pTMat ) + { + PushIllegalArgument(); + return; + } + } + + ScETSForecastCalculation aETSCalc( pMatX->GetElementCount(), pFormatter ); + if ( !aETSCalc.PreprocessDataRange( pMatX, pMatY, nSmplInPrd, bDataCompletion, + nAggregation, + ( eETSType != etsStatAdd && eETSType != etsStatMult ? pTMat : nullptr ), + eETSType ) ) + { + PushError( aETSCalc.GetError() ); + return; + } + + switch ( eETSType ) + { + case etsAdd : + case etsMult : + { + SCSIZE nC, nR; + pTMat->GetDimensions( nC, nR ); + ScMatrixRef pFcMat = GetNewMat( nC, nR, /*bEmpty*/true ); + aETSCalc.GetForecastRange( pTMat, pFcMat ); + if (aETSCalc.GetError() != FormulaError::NONE) + PushError( aETSCalc.GetError()); // explicitly push error, PushMatrix() does not + else + PushMatrix( pFcMat ); + } + break; + case etsPIAdd : + case etsPIMult : + { + SCSIZE nC, nR; + pTMat->GetDimensions( nC, nR ); + ScMatrixRef pPIMat = GetNewMat( nC, nR, /*bEmpty*/true ); + if ( nSmplInPrd == 0 ) + { + aETSCalc.GetEDSPredictionIntervals( pTMat, pPIMat, fPILevel ); + } + else + { + aETSCalc.GetETSPredictionIntervals( pTMat, pPIMat, fPILevel ); + } + if (aETSCalc.GetError() != FormulaError::NONE) + PushError( aETSCalc.GetError()); // explicitly push error, PushMatrix() does not + else + PushMatrix( pPIMat ); + } + break; + case etsStatAdd : + case etsStatMult : + { + SCSIZE nC, nR; + pTypeMat->GetDimensions( nC, nR ); + ScMatrixRef pStatMat = GetNewMat( nC, nR, /*bEmpty*/true ); + aETSCalc.GetStatisticValue( pTypeMat, pStatMat ); + if (aETSCalc.GetError() != FormulaError::NONE) + PushError( aETSCalc.GetError()); // explicitly push error, PushMatrix() does not + else + PushMatrix( pStatMat ); + } + break; + case etsSeason : + { + double rVal; + aETSCalc.GetSamplesInPeriod( rVal ); + SetError( aETSCalc.GetError() ); + PushDouble( rVal ); + } + break; + } +} + +void ScInterpreter::ScConcat_MS() +{ + OUStringBuffer aResBuf; + short nParamCount = GetByte(); + + //reverse order of parameter stack to simplify concatenation: + ReverseStack( nParamCount ); + + size_t nRefInList = 0; + while ( nParamCount-- > 0 && nGlobalError == FormulaError::NONE ) + { + switch ( GetStackType() ) + { + case svString: + case svDouble: + { + OUString aStr = GetString().getString(); + if (CheckStringResultLen(aResBuf, aStr.getLength())) + aResBuf.append(aStr); + } + break; + case svSingleRef : + { + ScAddress aAdr; + PopSingleRef( aAdr ); + if ( nGlobalError != FormulaError::NONE ) + break; + ScRefCellValue aCell( mrDoc, aAdr ); + if (!aCell.hasEmptyValue()) + { + svl::SharedString aSS; + GetCellString( aSS, aCell); + const OUString& rStr = aSS.getString(); + if (CheckStringResultLen(aResBuf, rStr.getLength())) + aResBuf.append( rStr); + } + } + break; + case svDoubleRef : + case svRefList : + { + ScRange aRange; + PopDoubleRef( aRange, nParamCount, nRefInList); + if ( nGlobalError != FormulaError::NONE ) + break; + // we need to read row for row, so we can't use ScCellIter + SCCOL nCol1, nCol2; + SCROW nRow1, nRow2; + SCTAB nTab1, nTab2; + aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + if ( nTab1 != nTab2 ) + { + SetError( FormulaError::IllegalParameter); + break; + } + PutInOrder( nRow1, nRow2 ); + PutInOrder( nCol1, nCol2 ); + ScAddress aAdr; + aAdr.SetTab( nTab1 ); + for ( SCROW nRow = nRow1; nRow <= nRow2; nRow++ ) + { + for ( SCCOL nCol = nCol1; nCol <= nCol2; nCol++ ) + { + aAdr.SetRow( nRow ); + aAdr.SetCol( nCol ); + ScRefCellValue aCell( mrDoc, aAdr ); + if (!aCell.hasEmptyValue() ) + { + svl::SharedString aSS; + GetCellString( aSS, aCell); + const OUString& rStr = aSS.getString(); + if (CheckStringResultLen(aResBuf, rStr.getLength())) + aResBuf.append( rStr); + } + } + } + } + break; + case svMatrix : + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatrixRef pMat = GetMatrix(); + if (pMat) + { + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + if (nC == 0 || nR == 0) + SetError(FormulaError::IllegalArgument); + else + { + for (SCSIZE k = 0; k < nR; ++k) + { + for (SCSIZE j = 0; j < nC; ++j) + { + if ( pMat->IsStringOrEmpty( j, k ) ) + { + OUString aStr = pMat->GetString( j, k ).getString(); + if (CheckStringResultLen(aResBuf, aStr.getLength())) + aResBuf.append(aStr); + } + else + { + if ( pMat->IsValue( j, k ) ) + { + OUString aStr = pMat->GetString( *pFormatter, j, k ).getString(); + if (CheckStringResultLen(aResBuf, aStr.getLength())) + aResBuf.append(aStr); + } + } + } + } + } + } + } + break; + default: + PopError(); + SetError( FormulaError::IllegalArgument); + break; + } + } + PushString( aResBuf.makeStringAndClear() ); +} + +void ScInterpreter::ScTextJoin_MS() +{ + short nParamCount = GetByte(); + + if ( !MustHaveParamCountMin( nParamCount, 3 ) ) + return; + + //reverse order of parameter stack to simplify processing + ReverseStack( nParamCount ); + + // get aDelimiters and bSkipEmpty + std::vector< OUString > aDelimiters; + size_t nRefInList = 0; + switch ( GetStackType() ) + { + case svString: + case svDouble: + aDelimiters.push_back( GetString().getString() ); + break; + case svSingleRef : + { + ScAddress aAdr; + PopSingleRef( aAdr ); + if ( nGlobalError != FormulaError::NONE ) + break; + ScRefCellValue aCell( mrDoc, aAdr ); + if (aCell.hasEmptyValue()) + aDelimiters.emplace_back(""); + else + { + svl::SharedString aSS; + GetCellString( aSS, aCell); + aDelimiters.push_back( aSS.getString()); + } + } + break; + case svDoubleRef : + case svRefList : + { + ScRange aRange; + PopDoubleRef( aRange, nParamCount, nRefInList); + if ( nGlobalError != FormulaError::NONE ) + break; + // we need to read row for row, so we can't use ScCellIterator + SCCOL nCol1, nCol2; + SCROW nRow1, nRow2; + SCTAB nTab1, nTab2; + aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + if ( nTab1 != nTab2 ) + { + SetError( FormulaError::IllegalParameter); + break; + } + PutInOrder( nRow1, nRow2 ); + PutInOrder( nCol1, nCol2 ); + ScAddress aAdr; + aAdr.SetTab( nTab1 ); + for ( SCROW nRow = nRow1; nRow <= nRow2; nRow++ ) + { + for ( SCCOL nCol = nCol1; nCol <= nCol2; nCol++ ) + { + aAdr.SetRow( nRow ); + aAdr.SetCol( nCol ); + ScRefCellValue aCell( mrDoc, aAdr ); + if (aCell.hasEmptyValue()) + aDelimiters.emplace_back(""); + else + { + svl::SharedString aSS; + GetCellString( aSS, aCell); + aDelimiters.push_back( aSS.getString()); + } + } + } + } + break; + case svMatrix : + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatrixRef pMat = GetMatrix(); + if (pMat) + { + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + if (nC == 0 || nR == 0) + SetError(FormulaError::IllegalArgument); + else + { + for (SCSIZE k = 0; k < nR; ++k) + { + for (SCSIZE j = 0; j < nC; ++j) + { + if (pMat->IsEmpty( j, k )) + aDelimiters.emplace_back(""); + else if (pMat->IsStringOrEmpty( j, k )) + aDelimiters.push_back( pMat->GetString( j, k ).getString() ); + else if (pMat->IsValue( j, k )) + aDelimiters.push_back( pMat->GetString( *pFormatter, j, k ).getString() ); + else + { + assert(!"should this really happen?"); + aDelimiters.emplace_back(""); + } + } + } + } + } + } + break; + default: + PopError(); + SetError( FormulaError::IllegalArgument); + break; + } + if ( aDelimiters.empty() ) + { + PushIllegalArgument(); + return; + } + SCSIZE nSize = aDelimiters.size(); + bool bSkipEmpty = static_cast< bool >( GetDouble() ); + nParamCount -= 2; + + OUStringBuffer aResBuf; + bool bFirst = true; + SCSIZE nIdx = 0; + nRefInList = 0; + // get the strings to be joined + while ( nParamCount-- > 0 && nGlobalError == FormulaError::NONE ) + { + switch ( GetStackType() ) + { + case svString: + case svDouble: + { + OUString aStr = GetString().getString(); + if ( !aStr.isEmpty() || !bSkipEmpty ) + { + if ( !bFirst ) + { + aResBuf.append( aDelimiters[ nIdx ] ); + if ( nSize > 1 ) + { + if ( ++nIdx >= nSize ) + nIdx = 0; + } + } + else + bFirst = false; + if (CheckStringResultLen(aResBuf, aStr.getLength())) + aResBuf.append( aStr ); + } + } + break; + case svSingleRef : + { + ScAddress aAdr; + PopSingleRef( aAdr ); + if ( nGlobalError != FormulaError::NONE ) + break; + ScRefCellValue aCell( mrDoc, aAdr ); + OUString aStr; + if (!aCell.hasEmptyValue()) + { + svl::SharedString aSS; + GetCellString( aSS, aCell); + aStr = aSS.getString(); + } + if ( !aStr.isEmpty() || !bSkipEmpty ) + { + if ( !bFirst ) + { + aResBuf.append( aDelimiters[ nIdx ] ); + if ( nSize > 1 ) + { + if ( ++nIdx >= nSize ) + nIdx = 0; + } + } + else + bFirst = false; + if (CheckStringResultLen(aResBuf, aStr.getLength())) + aResBuf.append( aStr ); + } + } + break; + case svDoubleRef : + case svRefList : + { + ScRange aRange; + PopDoubleRef( aRange, nParamCount, nRefInList); + if ( nGlobalError != FormulaError::NONE ) + break; + // we need to read row for row, so we can't use ScCellIterator + SCCOL nCol1, nCol2; + SCROW nRow1, nRow2; + SCTAB nTab1, nTab2; + aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + if ( nTab1 != nTab2 ) + { + SetError( FormulaError::IllegalParameter); + break; + } + PutInOrder( nRow1, nRow2 ); + PutInOrder( nCol1, nCol2 ); + ScAddress aAdr; + aAdr.SetTab( nTab1 ); + OUString aStr; + for ( SCROW nRow = nRow1; nRow <= nRow2; nRow++ ) + { + for ( SCCOL nCol = nCol1; nCol <= nCol2; nCol++ ) + { + aAdr.SetRow( nRow ); + aAdr.SetCol( nCol ); + ScRefCellValue aCell( mrDoc, aAdr ); + if (aCell.hasEmptyValue()) + aStr.clear(); + else + { + svl::SharedString aSS; + GetCellString( aSS, aCell); + aStr = aSS.getString(); + } + if ( !aStr.isEmpty() || !bSkipEmpty ) + { + if ( !bFirst ) + { + aResBuf.append( aDelimiters[ nIdx ] ); + if ( nSize > 1 ) + { + if ( ++nIdx >= nSize ) + nIdx = 0; + } + } + else + bFirst = false; + if (CheckStringResultLen(aResBuf, aStr.getLength())) + aResBuf.append( aStr ); + } + } + } + } + break; + case svMatrix : + case svExternalSingleRef: + case svExternalDoubleRef: + { + ScMatrixRef pMat = GetMatrix(); + if (pMat) + { + SCSIZE nC, nR; + pMat->GetDimensions(nC, nR); + if (nC == 0 || nR == 0) + SetError(FormulaError::IllegalArgument); + else + { + OUString aStr; + for (SCSIZE k = 0; k < nR; ++k) + { + for (SCSIZE j = 0; j < nC; ++j) + { + if (pMat->IsEmpty( j, k ) ) + aStr.clear(); + else if (pMat->IsStringOrEmpty( j, k )) + aStr = pMat->GetString( j, k ).getString(); + else if (pMat->IsValue( j, k )) + aStr = pMat->GetString( *pFormatter, j, k ).getString(); + else + { + assert(!"should this really happen?"); + aStr.clear(); + } + if ( !aStr.isEmpty() || !bSkipEmpty ) + { + if ( !bFirst ) + { + aResBuf.append( aDelimiters[ nIdx ] ); + if ( nSize > 1 ) + { + if ( ++nIdx >= nSize ) + nIdx = 0; + } + } + else + bFirst = false; + if (CheckStringResultLen(aResBuf, aStr.getLength())) + aResBuf.append( aStr ); + } + } + } + } + } + } + break; + case svMissing : + { + if ( !bSkipEmpty ) + { + if ( !bFirst ) + { + aResBuf.append( aDelimiters[ nIdx ] ); + if ( nSize > 1 ) + { + if ( ++nIdx >= nSize ) + nIdx = 0; + } + } + else + bFirst = false; + } + } + break; + default: + PopError(); + SetError( FormulaError::IllegalArgument); + break; + } + } + PushString( aResBuf.makeStringAndClear() ); +} + + +void ScInterpreter::ScIfs_MS() +{ + short nParamCount = GetByte(); + + ReverseStack( nParamCount ); + + nGlobalError = FormulaError::NONE; // propagate only for condition or active result path + bool bFinished = false; + while ( nParamCount > 0 && !bFinished && nGlobalError == FormulaError::NONE ) + { + bool bVal = GetBool(); + nParamCount--; + if ( bVal ) + { + // TRUE + if ( nParamCount < 1 ) + { + // no parameter given for THEN + PushParameterExpected(); + return; + } + bFinished = true; + } + else + { + // FALSE + if ( nParamCount >= 3 ) + { + // ELSEIF path + Pop(); + nParamCount--; + } + else + { + // no parameter given for ELSE + PushNA(); + return; + } + } + } + + if ( nGlobalError != FormulaError::NONE || !bFinished ) + { + if ( !bFinished ) + PushNA(); // no true expression found + if ( nGlobalError != FormulaError::NONE ) + PushNoValue(); // expression returned something other than true or false + return; + } + + //push result : + FormulaConstTokenRef xToken( PopToken() ); + if ( xToken ) + { + // Remove unused arguments of IFS from the stack before pushing the result. + while ( nParamCount > 1 ) + { + Pop(); + nParamCount--; + } + PushTokenRef( xToken ); + } + else + PushError( FormulaError::UnknownStackVariable ); +} + + +void ScInterpreter::ScSwitch_MS() +{ + short nParamCount = GetByte(); + + if (!MustHaveParamCountMin( nParamCount, 3)) + return; + + ReverseStack( nParamCount ); + + nGlobalError = FormulaError::NONE; // propagate only for match or active result path + bool isValue = false; + double fRefVal = 0; + svl::SharedString aRefStr; + switch ( GetStackType() ) + { + case svDouble: + isValue = true; + fRefVal = GetDouble(); + break; + case svString: + isValue = false; + aRefStr = GetString(); + break; + case svSingleRef : + case svDoubleRef : + { + ScAddress aAdr; + if (!PopDoubleRefOrSingleRef( aAdr )) + break; + ScRefCellValue aCell( mrDoc, aAdr ); + isValue = !( aCell.hasString() || aCell.hasEmptyValue() || aCell.isEmpty() ); + if ( isValue ) + fRefVal = GetCellValue( aAdr, aCell); + else + GetCellString( aRefStr, aCell); + } + break; + case svExternalSingleRef: + case svExternalDoubleRef: + case svMatrix: + isValue = ScMatrix::IsValueType( GetDoubleOrStringFromMatrix( fRefVal, aRefStr ) ); + break; + default : + PopError(); + PushIllegalArgument(); + return; + } + nParamCount--; + bool bFinished = false; + while ( nParamCount > 1 && !bFinished && nGlobalError == FormulaError::NONE ) + { + double fVal = 0; + svl::SharedString aStr; + if ( isValue ) + fVal = GetDouble(); + else + aStr = GetString(); + nParamCount--; + if ((nGlobalError != FormulaError::NONE && nParamCount < 2) + || (isValue && rtl::math::approxEqual( fRefVal, fVal)) + || (!isValue && aRefStr.getDataIgnoreCase() == aStr.getDataIgnoreCase())) + { + // TRUE + bFinished = true; + } + else + { + // FALSE + if ( nParamCount >= 2 ) + { + // ELSEIF path + Pop(); + nParamCount--; + // if nParamCount equals 1: default value to be returned + bFinished = ( nParamCount == 1 ); + } + else + { + // no parameter given for ELSE + PushNA(); + return; + } + nGlobalError = FormulaError::NONE; + } + } + + if ( nGlobalError != FormulaError::NONE || !bFinished ) + { + if ( !bFinished ) + PushNA(); // no true expression found + else + PushError( nGlobalError ); + return; + } + + // push result + FormulaConstTokenRef xToken( PopToken() ); + if ( xToken ) + { + // Remove unused arguments of SWITCH from the stack before pushing the result. + while ( nParamCount > 1 ) + { + Pop(); + nParamCount--; + } + PushTokenRef( xToken ); + } + else + PushError( FormulaError::UnknownStackVariable ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/interpretercontext.cxx b/sc/source/core/tool/interpretercontext.cxx new file mode 100644 index 0000000000..deb6f6d0ed --- /dev/null +++ b/sc/source/core/tool/interpretercontext.cxx @@ -0,0 +1,219 @@ +/* -*- 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 <interpretercontext.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> + +#include <document.hxx> +#include <formula/token.hxx> +#include <lookupcache.hxx> +#include <rangecache.hxx> +#include <algorithm> + +ScInterpreterContextPool ScInterpreterContextPool::aThreadedInterpreterPool(true); +ScInterpreterContextPool ScInterpreterContextPool::aNonThreadedInterpreterPool(false); + +ScInterpreterContext::ScInterpreterContext(const ScDocument& rDoc, SvNumberFormatter* pFormatter) + : mpDoc(&rDoc) + , mnTokenCachePos(0) + , maTokens(TOKEN_CACHE_SIZE, nullptr) + , pInterpreter(nullptr) + , mpFormatter(pFormatter) +{ +} + +ScInterpreterContext::~ScInterpreterContext() { ResetTokens(); } + +void ScInterpreterContext::ResetTokens() +{ + for (auto p : maTokens) + if (p) + p->DecRef(); + + mnTokenCachePos = 0; + std::fill(maTokens.begin(), maTokens.end(), nullptr); +} + +void ScInterpreterContext::SetDocAndFormatter(const ScDocument& rDoc, SvNumberFormatter* pFormatter) +{ + if (mpDoc != &rDoc) + { + mxScLookupCache.reset(); + mpDoc = &rDoc; + } + mpFormatter = pFormatter; +} + +void ScInterpreterContext::initFormatTable() +{ + mpFormatter = mpDoc->GetFormatTable(); // will assert if not main thread +} + +void ScInterpreterContext::Cleanup() +{ + // Do not disturb mxScLookupCache. + maConditions.clear(); + maDelayedSetNumberFormat.clear(); + ResetTokens(); +} + +void ScInterpreterContext::ClearLookupCache(const ScDocument* pDoc) +{ + if (pDoc == mpDoc) + mxScLookupCache.reset(); +} + +SvNumFormatType ScInterpreterContext::GetNumberFormatType(sal_uInt32 nFIndex) const +{ + if (!mpDoc->IsThreadedGroupCalcInProgress()) + { + return mpFormatter->GetType(nFIndex); + } + + if (maNFTypeCache.bIsValid && maNFTypeCache.nIndex == nFIndex) + { + return maNFTypeCache.eType; + } + + maNFTypeCache.nIndex = nFIndex; + maNFTypeCache.eType = mpFormatter->GetType(nFIndex); + maNFTypeCache.bIsValid = true; + return maNFTypeCache.eType; +} + +/* ScInterpreterContextPool */ + +// Threaded version +void ScInterpreterContextPool::Init(size_t nNumThreads, const ScDocument& rDoc, + SvNumberFormatter* pFormatter) +{ + assert(mbThreaded); + size_t nOldSize = maPool.size(); + maPool.resize(nNumThreads); + for (size_t nIdx = 0; nIdx < nNumThreads; ++nIdx) + { + if (nIdx >= nOldSize) + maPool[nIdx].reset(new ScInterpreterContext(rDoc, pFormatter)); + else + maPool[nIdx]->SetDocAndFormatter(rDoc, pFormatter); + } +} + +ScInterpreterContext* +ScInterpreterContextPool::GetInterpreterContextForThreadIdx(size_t nThreadIdx) const +{ + assert(mbThreaded); + assert(nThreadIdx < maPool.size()); + return maPool[nThreadIdx].get(); +} + +// Non-Threaded version +void ScInterpreterContextPool::Init(const ScDocument& rDoc, SvNumberFormatter* pFormatter) +{ + assert(!mbThreaded); + assert(mnNextFree <= maPool.size()); + bool bCreateNew = (maPool.size() == mnNextFree); + size_t nCurrIdx = mnNextFree; + if (bCreateNew) + { + maPool.resize(maPool.size() + 1); + maPool[nCurrIdx].reset(new ScInterpreterContext(rDoc, pFormatter)); + } + else + maPool[nCurrIdx]->SetDocAndFormatter(rDoc, pFormatter); + + ++mnNextFree; +} + +ScInterpreterContext* ScInterpreterContextPool::GetInterpreterContext() const +{ + assert(!mbThreaded); + assert(mnNextFree && (mnNextFree <= maPool.size())); + return maPool[mnNextFree - 1].get(); +} + +void ScInterpreterContextPool::ReturnToPool() +{ + if (mbThreaded) + { + for (size_t nIdx = 0; nIdx < maPool.size(); ++nIdx) + maPool[nIdx]->Cleanup(); + } + else + { + assert(mnNextFree && (mnNextFree <= maPool.size())); + --mnNextFree; + maPool[mnNextFree]->Cleanup(); + } +} + +// static +void ScInterpreterContextPool::ClearLookupCaches(const ScDocument* pDoc) +{ + for (auto& rPtr : aThreadedInterpreterPool.maPool) + rPtr->ClearLookupCache(pDoc); + for (auto& rPtr : aNonThreadedInterpreterPool.maPool) + rPtr->ClearLookupCache(pDoc); +} + +/* ScThreadedInterpreterContextGetterGuard */ + +ScThreadedInterpreterContextGetterGuard::ScThreadedInterpreterContextGetterGuard( + size_t nNumThreads, const ScDocument& rDoc, SvNumberFormatter* pFormatter) + : rPool(ScInterpreterContextPool::aThreadedInterpreterPool) +{ + rPool.Init(nNumThreads, rDoc, pFormatter); +} + +ScThreadedInterpreterContextGetterGuard::~ScThreadedInterpreterContextGetterGuard() +{ + rPool.ReturnToPool(); +} + +ScInterpreterContext* +ScThreadedInterpreterContextGetterGuard::GetInterpreterContextForThreadIdx(size_t nThreadIdx) const +{ + return rPool.GetInterpreterContextForThreadIdx(nThreadIdx); +} + +/* ScInterpreterContextGetterGuard */ + +ScInterpreterContextGetterGuard::ScInterpreterContextGetterGuard(const ScDocument& rDoc, + SvNumberFormatter* pFormatter) + : rPool(ScInterpreterContextPool::aNonThreadedInterpreterPool) +#if !defined NDEBUG + , nContextIdx(rPool.mnNextFree) +#endif +{ + rPool.Init(rDoc, pFormatter); +} + +ScInterpreterContextGetterGuard::~ScInterpreterContextGetterGuard() +{ + assert(nContextIdx == (rPool.mnNextFree - 1)); + rPool.ReturnToPool(); +} + +ScInterpreterContext* ScInterpreterContextGetterGuard::GetInterpreterContext() const +{ + return rPool.GetInterpreterContext(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/jumpmatrix.cxx b/sc/source/core/tool/jumpmatrix.cxx new file mode 100644 index 0000000000..868d5da655 --- /dev/null +++ b/sc/source/core/tool/jumpmatrix.cxx @@ -0,0 +1,273 @@ +/* -*- 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 <jumpmatrix.hxx> +#include <scmatrix.hxx> +#include <osl/diagnose.h> + +namespace { +// Don't bother with buffer overhead for less than y rows. +const SCSIZE kBufferThreshold = 128; +} + +ScJumpMatrix::ScJumpMatrix( OpCode eOp, SCSIZE nColsP, SCSIZE nRowsP ) + : mvJump(nColsP * nRowsP) + // Initialize result matrix in case of + // a premature end of the interpreter + // due to errors. + , pMat(new ScMatrix(nColsP, nRowsP, CreateDoubleError(FormulaError::NotAvailable))) + , nCols(nColsP) + , nRows(nRowsP) + , nCurCol(0) + , nCurRow(0) + , nResMatCols(nColsP) + , nResMatRows(nRowsP) + , meOp(eOp) + , bStarted(false) + , mnBufferCol(0) + , mnBufferRowStart(0) + , mnBufferEmptyCount(0) + , mnBufferEmptyPathCount(0) +{ + /*! pJump not initialized */ +} + +ScJumpMatrix::~ScJumpMatrix() +{ + for (const auto & i : mvParams) + i->DecRef(); +} + +void ScJumpMatrix::GetDimensions(SCSIZE& rCols, SCSIZE& rRows) const +{ + rCols = nCols; + rRows = nRows; +} + +void ScJumpMatrix::SetJump(SCSIZE nCol, SCSIZE nRow, double fBool, + short nStart, short nNext) +{ + mvJump[static_cast<sal_uInt64>(nCol) * nRows + nRow].SetJump(fBool, nStart, nNext, SHRT_MAX); +} + +void ScJumpMatrix::GetJump( + SCSIZE nCol, SCSIZE nRow, double& rBool, short& rStart, short& rNext, short& rStop) const +{ + if (nCols == 1 && nRows == 1) + { + nCol = 0; + nRow = 0; + } + else if (nCols == 1 && nRow < nRows) nCol = 0; + else if (nRows == 1 && nCol < nCols) nRow = 0; + else if (nCols <= nCol || nRows <= nRow) + { + OSL_FAIL("ScJumpMatrix::GetJump: dimension error"); + nCol = 0; + nRow = 0; + } + mvJump[static_cast<sal_uInt64>(nCol) * nRows + nRow]. + GetJump(rBool, rStart, rNext, rStop); +} + +void ScJumpMatrix::SetAllJumps(double fBool, short nStart, short nNext, short nStop) +{ + sal_uInt64 n = static_cast<sal_uInt64>(nCols) * nRows; + for (sal_uInt64 j = 0; j < n; ++j) + { + mvJump[j].SetJump(fBool, nStart, + nNext, nStop); + } +} + +void ScJumpMatrix::SetJumpParameters(ScTokenVec&& p) +{ + mvParams = std::move(p); +} + +void ScJumpMatrix::GetPos(SCSIZE& rCol, SCSIZE& rRow) const +{ + rCol = nCurCol; + rRow = nCurRow; +} + +bool ScJumpMatrix::Next(SCSIZE& rCol, SCSIZE& rRow) +{ + if (!bStarted) + { + bStarted = true; + nCurCol = nCurRow = 0; + } + else + { + if (++nCurRow >= nResMatRows) + { + nCurRow = 0; + ++nCurCol; + } + } + GetPos(rCol, rRow); + return nCurCol < nResMatCols; +} + +void ScJumpMatrix::GetResMatDimensions(SCSIZE& rCols, SCSIZE& rRows) +{ + rCols = nResMatCols; + rRows = nResMatRows; +} + +void ScJumpMatrix::SetNewResMat(SCSIZE nNewCols, SCSIZE nNewRows) +{ + if (nNewCols <= nResMatCols && nNewRows <= nResMatRows) + return; + + FlushBufferOtherThan( BUFFER_NONE, 0, 0); + pMat = pMat->CloneAndExtend(nNewCols, nNewRows); + if (nResMatCols < nNewCols) + { + pMat->FillDouble( + CreateDoubleError(FormulaError::NotAvailable), + nResMatCols, 0, nNewCols - 1, nResMatRows - 1); + } + if (nResMatRows < nNewRows) + { + pMat->FillDouble( + CreateDoubleError(FormulaError::NotAvailable), + 0, nResMatRows, nNewCols - 1, nNewRows - 1); + } + if (nRows == 1 && nCurCol != 0) + { + nCurCol = 0; + nCurRow = nResMatRows - 1; + } + nResMatCols = nNewCols; + nResMatRows = nNewRows; +} + +bool ScJumpMatrix::HasResultMatrix() const +{ + // We now always have a matrix but caller logic may still want to check it. + return bool(pMat); +} + +ScRefList& ScJumpMatrix::GetRefList() +{ + return mvRefList; +} + +void ScJumpMatrix::FlushBufferOtherThan( ScJumpMatrix::BufferType eType, SCSIZE nC, SCSIZE nR ) +{ + if (!mvBufferDoubles.empty() && + (eType != BUFFER_DOUBLE || nC != mnBufferCol || nR != mnBufferRowStart + mvBufferDoubles.size())) + { + pMat->PutDoubleVector( mvBufferDoubles, mnBufferCol, mnBufferRowStart); + mvBufferDoubles.clear(); + } + if (!mvBufferStrings.empty() && + (eType != BUFFER_STRING || nC != mnBufferCol || nR != mnBufferRowStart + mvBufferStrings.size())) + { + pMat->PutStringVector( mvBufferStrings, mnBufferCol, mnBufferRowStart); + mvBufferStrings.clear(); + } + if (mnBufferEmptyCount && + (eType != BUFFER_EMPTY || nC != mnBufferCol || nR != mnBufferRowStart + mnBufferEmptyCount)) + { + pMat->PutEmptyVector( mnBufferEmptyCount, mnBufferCol, mnBufferRowStart); + mnBufferEmptyCount = 0; + } + if (mnBufferEmptyPathCount && + (eType != BUFFER_EMPTYPATH || nC != mnBufferCol || nR != mnBufferRowStart + mnBufferEmptyPathCount)) + { + pMat->PutEmptyPathVector( mnBufferEmptyPathCount, mnBufferCol, mnBufferRowStart); + mnBufferEmptyPathCount = 0; + } +} + +ScMatrix* ScJumpMatrix::GetResultMatrix() +{ + if (nResMatRows >= kBufferThreshold) + FlushBufferOtherThan( BUFFER_NONE, 0, 0); + return pMat.get(); +} + +void ScJumpMatrix::PutResultDouble( double fVal, SCSIZE nC, SCSIZE nR ) +{ + if (nResMatRows < kBufferThreshold) + pMat->PutDouble( fVal, nC, nR); + else + { + FlushBufferOtherThan( BUFFER_DOUBLE, nC, nR); + if (mvBufferDoubles.empty()) + { + mnBufferCol = nC; + mnBufferRowStart = nR; + } + mvBufferDoubles.push_back( fVal); + } +} + +void ScJumpMatrix::PutResultString( const svl::SharedString& rStr, SCSIZE nC, SCSIZE nR ) +{ + if (nResMatRows < kBufferThreshold) + pMat->PutString( rStr, nC, nR); + else + { + FlushBufferOtherThan( BUFFER_STRING, nC, nR); + if (mvBufferStrings.empty()) + { + mnBufferCol = nC; + mnBufferRowStart = nR; + } + mvBufferStrings.push_back( rStr); + } +} + +void ScJumpMatrix::PutResultEmpty( SCSIZE nC, SCSIZE nR ) +{ + if (nResMatRows < kBufferThreshold) + pMat->PutEmpty( nC, nR); + else + { + FlushBufferOtherThan( BUFFER_EMPTY, nC, nR); + if (!mnBufferEmptyCount) + { + mnBufferCol = nC; + mnBufferRowStart = nR; + } + ++mnBufferEmptyCount; + } +} + +void ScJumpMatrix::PutResultEmptyPath( SCSIZE nC, SCSIZE nR ) +{ + if (nResMatRows < kBufferThreshold) + pMat->PutEmptyPath( nC, nR); + else + { + FlushBufferOtherThan( BUFFER_EMPTYPATH, nC, nR); + if (!mnBufferEmptyPathCount) + { + mnBufferCol = nC; + mnBufferRowStart = nR; + } + ++mnBufferEmptyPathCount; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/listenerquery.cxx b/sc/source/core/tool/listenerquery.cxx new file mode 100644 index 0000000000..10cebdd421 --- /dev/null +++ b/sc/source/core/tool/listenerquery.cxx @@ -0,0 +1,90 @@ +/* -*- 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/. + */ + +#include <listenerquery.hxx> +#include <listenerqueryids.hxx> +#include <address.hxx> + +namespace sc { + +RefQueryFormulaGroup::RefQueryFormulaGroup() : + SvtListener::QueryBase(SC_LISTENER_QUERY_FORMULA_GROUP_POS), + maSkipRange(ScAddress::INITIALIZE_INVALID) {} + +RefQueryFormulaGroup::~RefQueryFormulaGroup() {} + +void RefQueryFormulaGroup::setSkipRange( const ScRange& rRange ) +{ + maSkipRange = rRange; +} + +void RefQueryFormulaGroup::add( const ScAddress& rPos ) +{ + if (!rPos.IsValid()) + return; + + if (maSkipRange.IsValid() && maSkipRange.Contains(rPos)) + // This is within the skip range. Skip it. + return; + + TabsType::iterator itTab = maTabs.find(rPos.Tab()); + if (itTab == maTabs.end()) + { + std::pair<TabsType::iterator,bool> r = + maTabs.emplace(rPos.Tab(), ColsType()); + if (!r.second) + // Insertion failed. + return; + + itTab = r.first; + } + + ColsType& rCols = itTab->second; + ColsType::iterator itCol = rCols.find(rPos.Col()); + if (itCol == rCols.end()) + { + std::pair<ColsType::iterator,bool> r = + rCols.emplace(rPos.Col(), ColType()); + if (!r.second) + // Insertion failed. + return; + + itCol = r.first; + } + + ColType& rCol = itCol->second; + rCol.push_back(rPos.Row()); +} + +const RefQueryFormulaGroup::TabsType& RefQueryFormulaGroup::getAllPositions() const +{ + return maTabs; +} + +QueryRange::QueryRange() : + SvtListener::QueryBase(SC_LISTENER_QUERY_FORMULA_GROUP_RANGE) +{} + +QueryRange::~QueryRange() +{ +} + +void QueryRange::add( const ScRange& rRange ) +{ + maRanges.Join(rRange); +} + +void QueryRange::swapRanges( ScRangeList& rRanges ) +{ + maRanges.swap(rRanges); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/lookupcache.cxx b/sc/source/core/tool/lookupcache.cxx new file mode 100644 index 0000000000..70e19ec20d --- /dev/null +++ b/sc/source/core/tool/lookupcache.cxx @@ -0,0 +1,127 @@ +/* -*- 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 <lookupcache.hxx> +#include <document.hxx> +#include <queryentry.hxx> +#include <brdcst.hxx> + +#include <sal/log.hxx> + +ScLookupCache::QueryCriteria::QueryCriteria( const ScQueryEntry& rEntry ) : + mfVal(0.0), mbAlloc(false), mbString(false) +{ + switch (rEntry.eOp) + { + case SC_EQUAL : + meOp = EQUAL; + break; + case SC_LESS_EQUAL : + meOp = LESS_EQUAL; + break; + case SC_GREATER_EQUAL : + meOp = GREATER_EQUAL; + break; + default: + meOp = UNKNOWN; + SAL_WARN( "sc.core", "ScLookupCache::QueryCriteria not prepared for this ScQueryOp"); + } + + const ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + if (rItem.meType == ScQueryEntry::ByString) + setString(rItem.maString.getString()); + else + setDouble(rItem.mfVal); +} + +ScLookupCache::QueryCriteria::QueryCriteria( const ScLookupCache::QueryCriteria & r ) : + mfVal( r.mfVal), + mbAlloc( false), + mbString( false), + meOp( r.meOp) +{ + if (r.mbString && r.mpStr) + { + mpStr = new OUString( *r.mpStr); + mbAlloc = mbString = true; + } +} + +ScLookupCache::QueryCriteria::~QueryCriteria() +{ + deleteString(); +} + +ScLookupCache::Result ScLookupCache::lookup( ScAddress & o_rResultAddress, + const QueryCriteria & rCriteria, const ScAddress & rQueryAddress ) const +{ + auto it( maQueryMap.find( QueryKey( rQueryAddress, + rCriteria.getQueryOp()))); + if (it == maQueryMap.end()) + return NOT_CACHED; + const QueryCriteriaAndResult& rResult = (*it).second; + if (!(rResult.maCriteria == rCriteria)) + return CRITERIA_DIFFERENT; + if (rResult.maAddress.Row() < 0 ) + return NOT_AVAILABLE; + o_rResultAddress = rResult.maAddress; + return FOUND; +} + +SCROW ScLookupCache::lookup( const QueryCriteria & rCriteria ) const +{ + // try to find the row index for which we have already performed lookup + auto it = std::find_if(maQueryMap.begin(), maQueryMap.end(), + [&rCriteria](const std::pair<QueryKey, QueryCriteriaAndResult>& rEntry) { + return rEntry.second.maCriteria == rCriteria; + }); + if (it != maQueryMap.end()) + return it->first.mnRow; + + // not found + return -1; +} + +bool ScLookupCache::insert( const ScAddress & rResultAddress, + const QueryCriteria & rCriteria, const ScAddress & rQueryAddress, + const bool bAvailable ) +{ + QueryKey aKey( rQueryAddress, rCriteria.getQueryOp()); + QueryCriteriaAndResult aResult( rCriteria, rResultAddress); + if (!bAvailable) + aResult.maAddress.SetRow(-1); + bool bInserted = maQueryMap.insert( ::std::pair< const QueryKey, + QueryCriteriaAndResult>( aKey, aResult)).second; + + return bInserted; +} + +void ScLookupCache::Notify( const SfxHint& rHint ) +{ + if (!mpDoc->IsInDtorClear()) + { + if (rHint.GetId() == SfxHintId::ScDataChanged || rHint.GetId() == SfxHintId::ScAreaChanged) + { + mpDoc->RemoveLookupCache( *this); + // this ScLookupCache is deleted by RemoveLookupCache + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/math.cxx b/sc/source/core/tool/math.cxx new file mode 100644 index 0000000000..e61d39386e --- /dev/null +++ b/sc/source/core/tool/math.cxx @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <math.hxx> +#include <cmath> +#include <cerrno> +#include <cfenv> + +#include <o3tl/float_int_conversion.hxx> +#include <rtl/math.hxx> + +namespace sc +{ +static double err_pow(const double& fVal1, const double& fVal2) +{ + // pow() is expected to set domain error or pole error or range error (or + // flag them via exceptions) or return NaN or Inf. + assert((math_errhandling & (MATH_ERRNO | MATH_ERREXCEPT)) != 0); + std::feclearexcept(FE_ALL_EXCEPT); + errno = 0; + return pow(fVal1, fVal2); +} + +double power(const double& fVal1, const double& fVal2) +{ + double fPow; + if (fVal1 < 0 && fVal2 != 0.0) + { + const double f = 1.0 / fVal2 + ((fVal2 < 0.0) ? -0.5 : 0.5); + if (!(o3tl::convertsToAtLeast(f, SAL_MIN_INT64) + && o3tl::convertsToAtMost(f, SAL_MAX_INT64))) + { + // Casting to int would be undefined behaviour. + fPow = err_pow(fVal1, fVal2); + } + else + { + const sal_Int64 i = static_cast<sal_Int64>(f); + if (i % 2 != 0 && rtl::math::approxEqual(1 / static_cast<double>(i), fVal2)) + fPow = -err_pow(-fVal1, fVal2); + else + fPow = err_pow(fVal1, fVal2); + } + } + else + { + fPow = err_pow(fVal1, fVal2); + } + // The pow() call must had been the most recent call to check errno or exception. + if ((((math_errhandling & MATH_ERRNO) != 0) && (errno == EDOM || errno == ERANGE)) +// emscripten is currently broken by https://github.com/emscripten-core/emscripten/pull/11087 +// While the removal is correct for C99, it's not for C++11 (see http://www.cplusplus.com/reference/cfenv/FE_INEXACT/) +// But since emscripten currently doesn't support any math exceptions, we simply ignore them +#ifndef __EMSCRIPTEN__ + || (((math_errhandling & MATH_ERREXCEPT) != 0) + && std::fetestexcept(FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW)) +#endif + || !std::isfinite(fPow)) + { + fPow = CreateDoubleError(FormulaError::IllegalFPOperation); + } + return fPow; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sc/source/core/tool/matrixoperators.cxx b/sc/source/core/tool/matrixoperators.cxx new file mode 100644 index 0000000000..9406d09255 --- /dev/null +++ b/sc/source/core/tool/matrixoperators.cxx @@ -0,0 +1,41 @@ +/* -*- 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/. + */ + +#include <matrixoperators.hxx> + +namespace sc::op +{ +/* Simple operators */ + +void Sum::operator()(KahanSum& rAccum, double fVal) const { rAccum += fVal; } + +const double Sum::InitVal = 0.0; + +void SumSquare::operator()(KahanSum& rAccum, double fVal) const { rAccum += fVal * fVal; } + +const double SumSquare::InitVal = 0.0; + +void Product::operator()(double& rAccum, double fVal) const { rAccum *= fVal; } + +const double Product::InitVal = 1.0; + +/* Op operators */ + +void fkOpSum(KahanSum& rAccum, double fVal) { rAccum += fVal; } + +kOp kOpSum(0.0, fkOpSum); + +void fkOpSumSquare(KahanSum& rAccum, double fVal) { rAccum += fVal * fVal; } + +kOp kOpSumSquare(0.0, fkOpSumSquare); + +std::vector<kOp> kOpSumAndSumSquare = { kOpSum, kOpSumSquare }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/navicfg.cxx b/sc/source/core/tool/navicfg.cxx new file mode 100644 index 0000000000..ad8e295800 --- /dev/null +++ b/sc/source/core/tool/navicfg.cxx @@ -0,0 +1,60 @@ +/* -*- 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 <navicfg.hxx> +#include <content.hxx> + +//TODO: #define CFGPATH_NAVIPI "Office.Calc/Navigator" + +ScNavipiCfg::ScNavipiCfg() : +//TODO: ConfigItem( OUString( CFGPATH_NAVIPI ) ), + nListMode(0), + nDragMode(0), + nRootType(ScContentId::ROOT) +{ +} + +void ScNavipiCfg::SetListMode(sal_uInt16 nNew) +{ + if ( nListMode != nNew ) + { + nListMode = nNew; +//TODO: SetModified(); + } +} + +void ScNavipiCfg::SetDragMode(sal_uInt16 nNew) +{ + if ( nDragMode != nNew ) + { + nDragMode = nNew; +//TODO: SetModified(); + } +} + +void ScNavipiCfg::SetRootType(ScContentId nNew) +{ + if ( nRootType != nNew ) + { + nRootType = nNew; +//TODO: SetModified(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/numformat.cxx b/sc/source/core/tool/numformat.cxx new file mode 100644 index 0000000000..c69da6de4e --- /dev/null +++ b/sc/source/core/tool/numformat.cxx @@ -0,0 +1,71 @@ +/* -*- 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 <numformat.hxx> +#include <patattr.hxx> +#include <document.hxx> + +#include <comphelper/processfactory.hxx> +#include <svl/numformat.hxx> +#include <svl/zformat.hxx> +#include <svl/languageoptions.hxx> +#include <optional> + +namespace +{ + const OUString& getNumDecimalSep(const SvNumberformat& rFormat) + { + LanguageType nFormatLang = rFormat.GetLanguage(); + if (nFormatLang == LANGUAGE_SYSTEM) + return ScGlobal::getLocaleData().getNumDecimalSep(); + // LocaleDataWrapper can be expensive to construct, so cache the result for + // repeated calls + static std::optional<LocaleDataWrapper> localeCache; + if (!localeCache || localeCache->getLanguageTag().getLanguageType() != nFormatLang) + localeCache.emplace( + comphelper::getProcessComponentContext(), LanguageTag(nFormatLang)); + return localeCache->getNumDecimalSep(); + } +} + +namespace sc { + +bool NumFmtUtil::isLatinScript( const ScPatternAttr& rPat, ScDocument& rDoc ) +{ + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + sal_uInt32 nKey = rPat.GetNumberFormat(pFormatter); + return isLatinScript(nKey, rDoc); +} + +bool NumFmtUtil::isLatinScript( sal_uLong nFormat, ScDocument& rDoc ) +{ + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + const SvNumberformat* pFormat = pFormatter->GetEntry(nFormat); + if (!pFormat || !pFormat->IsStandard()) + return false; + + // The standard format is all-latin if the decimal separator doesn't + // have a different script type + SvtScriptType nScript = rDoc.GetStringScriptType(getNumDecimalSep(*pFormat)); + return (nScript == SvtScriptType::NONE || nScript == SvtScriptType::LATIN); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/odffmap.cxx b/sc/source/core/tool/odffmap.cxx new file mode 100644 index 0000000000..7fc4ac5141 --- /dev/null +++ b/sc/source/core/tool/odffmap.cxx @@ -0,0 +1,142 @@ +/* -*- 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 <compiler.hxx> + +// ODFF, English, Programmatical, ODF_11 +// Functions duplicated to internal when writing ODFF are listed in static const XclFunctionInfo saFuncTable_4[] +const ScCompiler::AddInMap ScCompiler::g_aAddInMap[] = +{ + { "ORG.OPENOFFICE.WEEKS", "WEEKS", "com.sun.star.sheet.addin.DateFunctions.getDiffWeeks", "COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETDIFFWEEKS" }, + { "ORG.OPENOFFICE.MONTHS", "MONTHS", "com.sun.star.sheet.addin.DateFunctions.getDiffMonths", "COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETDIFFMONTHS" }, + { "ORG.OPENOFFICE.YEARS", "YEARS", "com.sun.star.sheet.addin.DateFunctions.getDiffYears", "COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETDIFFYEARS" }, + { "ORG.OPENOFFICE.ISLEAPYEAR", "ISLEAPYEAR", "com.sun.star.sheet.addin.DateFunctions.getIsLeapYear", "COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETISLEAPYEAR" }, + { "ORG.OPENOFFICE.DAYSINMONTH", "DAYSINMONTH", "com.sun.star.sheet.addin.DateFunctions.getDaysInMonth", "COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETDAYSINMONTH" }, + { "ORG.OPENOFFICE.DAYSINYEAR", "DAYSINYEAR", "com.sun.star.sheet.addin.DateFunctions.getDaysInYear", "COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETDAYSINYEAR" }, + { "ORG.OPENOFFICE.WEEKSINYEAR", "WEEKSINYEAR", "com.sun.star.sheet.addin.DateFunctions.getWeeksInYear", "COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETWEEKSINYEAR" }, + { "ORG.OPENOFFICE.ROT13", "ROT13", "com.sun.star.sheet.addin.DateFunctions.getRot13", "COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETROT13" }, + { "WORKDAY", "WORKDAY", "com.sun.star.sheet.addin.Analysis.getWorkday", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETWORKDAY" }, + { "YEARFRAC", "YEARFRAC", "com.sun.star.sheet.addin.Analysis.getYearfrac", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETYEARFRAC" }, + { "EDATE", "EDATE", "com.sun.star.sheet.addin.Analysis.getEdate", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETEDATE" }, + { "WEEKNUM", "WEEKNUM_EXCEL2003", "com.sun.star.sheet.addin.Analysis.getWeeknum", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETWEEKNUM" }, + { "EOMONTH", "EOMONTH", "com.sun.star.sheet.addin.Analysis.getEomonth", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETEOMONTH" }, + { "NETWORKDAYS", "NETWORKDAYS_EXCEL2003", "com.sun.star.sheet.addin.Analysis.getNetworkdays", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETNETWORKDAYS" }, + { "ISEVEN", "ISEVEN_ADD", "com.sun.star.sheet.addin.Analysis.getIseven", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETISEVEN" }, + { "ISODD", "ISODD_ADD", "com.sun.star.sheet.addin.Analysis.getIsodd", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETISODD" }, + { "MULTINOMIAL", "MULTINOMIAL", "com.sun.star.sheet.addin.Analysis.getMultinomial", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETMULTINOMIAL" }, + { "SERIESSUM", "SERIESSUM", "com.sun.star.sheet.addin.Analysis.getSeriessum", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETSERIESSUM" }, + { "QUOTIENT", "QUOTIENT", "com.sun.star.sheet.addin.Analysis.getQuotient", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETQUOTIENT" }, + { "MROUND", "MROUND", "com.sun.star.sheet.addin.Analysis.getMround", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETMROUND" }, + { "SQRTPI", "SQRTPI", "com.sun.star.sheet.addin.Analysis.getSqrtpi", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETSQRTPI" }, + { "RANDBETWEEN", "RANDBETWEEN", "com.sun.star.sheet.addin.Analysis.getRandbetween", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETRANDBETWEEN" }, + { "GCD", "GCD_EXCEL2003", "com.sun.star.sheet.addin.Analysis.getGcd", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETGCD" }, + { "LCM", "LCM_EXCEL2003", "com.sun.star.sheet.addin.Analysis.getLcm", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETLCM" }, + { "BESSELI", "BESSELI", "com.sun.star.sheet.addin.Analysis.getBesseli", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETBESSELI" }, + { "BESSELJ", "BESSELJ", "com.sun.star.sheet.addin.Analysis.getBesselj", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETBESSELJ" }, + { "BESSELK", "BESSELK", "com.sun.star.sheet.addin.Analysis.getBesselk", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETBESSELK" }, + { "BESSELY", "BESSELY", "com.sun.star.sheet.addin.Analysis.getBessely", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETBESSELY" }, + { "BIN2OCT", "BIN2OCT", "com.sun.star.sheet.addin.Analysis.getBin2Oct", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETBIN2OCT" }, + { "BIN2DEC", "BIN2DEC", "com.sun.star.sheet.addin.Analysis.getBin2Dec", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETBIN2DEC" }, + { "BIN2HEX", "BIN2HEX", "com.sun.star.sheet.addin.Analysis.getBin2Hex", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETBIN2HEX" }, + { "OCT2BIN", "OCT2BIN", "com.sun.star.sheet.addin.Analysis.getOct2Bin", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETOCT2BIN" }, + { "OCT2DEC", "OCT2DEC", "com.sun.star.sheet.addin.Analysis.getOct2Dec", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETOCT2DEC" }, + { "OCT2HEX", "OCT2HEX", "com.sun.star.sheet.addin.Analysis.getOct2Hex", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETOCT2HEX" }, + { "DEC2BIN", "DEC2BIN", "com.sun.star.sheet.addin.Analysis.getDec2Bin", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETDEC2BIN" }, + { "DEC2OCT", "DEC2OCT", "com.sun.star.sheet.addin.Analysis.getDec2Oct", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETDEC2OCT" }, + { "DEC2HEX", "DEC2HEX", "com.sun.star.sheet.addin.Analysis.getDec2Hex", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETDEC2HEX" }, + { "HEX2BIN", "HEX2BIN", "com.sun.star.sheet.addin.Analysis.getHex2Bin", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETHEX2BIN" }, + { "HEX2DEC", "HEX2DEC", "com.sun.star.sheet.addin.Analysis.getHex2Dec", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETHEX2DEC" }, + { "HEX2OCT", "HEX2OCT", "com.sun.star.sheet.addin.Analysis.getHex2Oct", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETHEX2OCT" }, + { "DELTA", "DELTA", "com.sun.star.sheet.addin.Analysis.getDelta", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETDELTA" }, + { "ERF", "ERF", "com.sun.star.sheet.addin.Analysis.getErf", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETERF" }, + { "ERFC", "ERFC", "com.sun.star.sheet.addin.Analysis.getErfc", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETERFC" }, + { "GESTEP", "GESTEP", "com.sun.star.sheet.addin.Analysis.getGestep", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETGESTEP" }, + { "FACTDOUBLE", "FACTDOUBLE", "com.sun.star.sheet.addin.Analysis.getFactdouble", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETFACTDOUBLE" }, + { "IMABS", "IMABS", "com.sun.star.sheet.addin.Analysis.getImabs", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMABS" }, + { "IMAGINARY", "IMAGINARY", "com.sun.star.sheet.addin.Analysis.getImaginary", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMAGINARY" }, + { "IMPOWER", "IMPOWER", "com.sun.star.sheet.addin.Analysis.getImpower", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMPOWER" }, + { "IMARGUMENT", "IMARGUMENT", "com.sun.star.sheet.addin.Analysis.getImargument", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMARGUMENT" }, + { "IMCOS", "IMCOS", "com.sun.star.sheet.addin.Analysis.getImcos", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMCOS" }, + { "IMDIV", "IMDIV", "com.sun.star.sheet.addin.Analysis.getImdiv", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMDIV" }, + { "IMEXP", "IMEXP", "com.sun.star.sheet.addin.Analysis.getImexp", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMEXP" }, + { "IMCONJUGATE", "IMCONJUGATE", "com.sun.star.sheet.addin.Analysis.getImconjugate", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMCONJUGATE" }, + { "IMLN", "IMLN", "com.sun.star.sheet.addin.Analysis.getImln", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMLN" }, + { "IMLOG10", "IMLOG10", "com.sun.star.sheet.addin.Analysis.getImlog10", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMLOG10" }, + { "IMLOG2", "IMLOG2", "com.sun.star.sheet.addin.Analysis.getImlog2", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMLOG2" }, + { "IMPRODUCT", "IMPRODUCT", "com.sun.star.sheet.addin.Analysis.getImproduct", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMPRODUCT" }, + { "IMREAL", "IMREAL", "com.sun.star.sheet.addin.Analysis.getImreal", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMREAL" }, + { "IMSIN", "IMSIN", "com.sun.star.sheet.addin.Analysis.getImsin", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMSIN" }, + { "IMSUB", "IMSUB", "com.sun.star.sheet.addin.Analysis.getImsub", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMSUB" }, + { "IMSUM", "IMSUM", "com.sun.star.sheet.addin.Analysis.getImsum", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMSUM" }, + { "IMSQRT", "IMSQRT", "com.sun.star.sheet.addin.Analysis.getImsqrt", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMSQRT" }, + { "IMTAN", "IMTAN", "com.sun.star.sheet.addin.Analysis.getImtan", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMTAN" }, + { "IMSEC", "IMSEC", "com.sun.star.sheet.addin.Analysis.getImsec", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMSEC" }, + { "IMCSC", "IMCSC", "com.sun.star.sheet.addin.Analysis.getImcsc", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMCSC" }, + { "IMCOT", "IMCOT", "com.sun.star.sheet.addin.Analysis.getImcot", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMCOT" }, + { "IMSINH", "IMSINH", "com.sun.star.sheet.addin.Analysis.getImsinh", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMSINH" }, + { "IMCOSH", "IMCOSH", "com.sun.star.sheet.addin.Analysis.getImcosh", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMCOSH" }, + { "IMSECH", "IMSECH", "com.sun.star.sheet.addin.Analysis.getImsech", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMSECH" }, + { "IMCSCH", "IMCSCH", "com.sun.star.sheet.addin.Analysis.getImcsch", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETIMCSCH" }, + { "COMPLEX", "COMPLEX", "com.sun.star.sheet.addin.Analysis.getComplex", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETCOMPLEX" }, + { "CONVERT", "CONVERT", "com.sun.star.sheet.addin.Analysis.getConvert", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETCONVERT" }, + { "AMORDEGRC", "AMORDEGRC", "com.sun.star.sheet.addin.Analysis.getAmordegrc", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETAMORDEGRC" }, + { "AMORLINC", "AMORLINC", "com.sun.star.sheet.addin.Analysis.getAmorlinc", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETAMORLINC" }, + { "ACCRINT", "ACCRINT", "com.sun.star.sheet.addin.Analysis.getAccrint", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETACCRINT" }, + { "ACCRINTM", "ACCRINTM", "com.sun.star.sheet.addin.Analysis.getAccrintm", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETACCRINTM" }, + { "RECEIVED", "RECEIVED", "com.sun.star.sheet.addin.Analysis.getReceived", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETRECEIVED" }, + { "DISC", "DISC", "com.sun.star.sheet.addin.Analysis.getDisc", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETDISC" }, + { "DURATION", "DURATION", "com.sun.star.sheet.addin.Analysis.getDuration", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETDURATION" }, + { "EFFECT", "EFFECT_ADD", "com.sun.star.sheet.addin.Analysis.getEffect", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETEFFECT" }, + { "CUMPRINC", "CUMPRINC_ADD", "com.sun.star.sheet.addin.Analysis.getCumprinc", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETCUMPRINC" }, + { "CUMIPMT", "CUMIPMT_ADD", "com.sun.star.sheet.addin.Analysis.getCumipmt", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETCUMIPMT" }, + { "PRICE", "PRICE", "com.sun.star.sheet.addin.Analysis.getPrice", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETPRICE" }, + { "PRICEDISC", "PRICEDISC", "com.sun.star.sheet.addin.Analysis.getPricedisc", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETPRICEDISC" }, + { "PRICEMAT", "PRICEMAT", "com.sun.star.sheet.addin.Analysis.getPricemat", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETPRICEMAT" }, + { "MDURATION", "MDURATION", "com.sun.star.sheet.addin.Analysis.getMduration", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETMDURATION" }, + { "NOMINAL", "NOMINAL_ADD", "com.sun.star.sheet.addin.Analysis.getNominal", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETNOMINAL" }, + { "DOLLARFR", "DOLLARFR", "com.sun.star.sheet.addin.Analysis.getDollarfr", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETDOLLARFR" }, + { "DOLLARDE", "DOLLARDE", "com.sun.star.sheet.addin.Analysis.getDollarde", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETDOLLARDE" }, + { "YIELD", "YIELD", "com.sun.star.sheet.addin.Analysis.getYield", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETYIELD" }, + { "YIELDDISC", "YIELDDISC", "com.sun.star.sheet.addin.Analysis.getYielddisc", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETYIELDDISC" }, + { "YIELDMAT", "YIELDMAT", "com.sun.star.sheet.addin.Analysis.getYieldmat", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETYIELDMAT" }, + { "TBILLEQ", "TBILLEQ", "com.sun.star.sheet.addin.Analysis.getTbilleq", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETTBILLEQ" }, + { "TBILLPRICE", "TBILLPRICE", "com.sun.star.sheet.addin.Analysis.getTbillprice", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETTBILLPRICE" }, + { "TBILLYIELD", "TBILLYIELD", "com.sun.star.sheet.addin.Analysis.getTbillyield", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETTBILLYIELD" }, + { "ODDFPRICE", "ODDFPRICE", "com.sun.star.sheet.addin.Analysis.getOddfprice", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETODDFPRICE" }, + { "ODDFYIELD", "ODDFYIELD", "com.sun.star.sheet.addin.Analysis.getOddfyield", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETODDFYIELD" }, + { "ODDLPRICE", "ODDLPRICE", "com.sun.star.sheet.addin.Analysis.getOddlprice", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETODDLPRICE" }, + { "ODDLYIELD", "ODDLYIELD", "com.sun.star.sheet.addin.Analysis.getOddlyield", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETODDLYIELD" }, + { "XIRR", "XIRR", "com.sun.star.sheet.addin.Analysis.getXirr", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETXIRR" }, + { "XNPV", "XNPV", "com.sun.star.sheet.addin.Analysis.getXnpv", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETXNPV" }, + { "INTRATE", "INTRATE", "com.sun.star.sheet.addin.Analysis.getIntrate", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETINTRATE" }, + { "COUPNCD", "COUPNCD", "com.sun.star.sheet.addin.Analysis.getCoupncd", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETCOUPNCD" }, + { "COUPDAYS", "COUPDAYS", "com.sun.star.sheet.addin.Analysis.getCoupdays", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETCOUPDAYS" }, + { "COUPDAYSNC", "COUPDAYSNC", "com.sun.star.sheet.addin.Analysis.getCoupdaysnc", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETCOUPDAYSNC" }, + { "COUPDAYBS", "COUPDAYBS", "com.sun.star.sheet.addin.Analysis.getCoupdaybs", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETCOUPDAYBS" }, + { "COUPPCD", "COUPPCD", "com.sun.star.sheet.addin.Analysis.getCouppcd", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETCOUPPCD" }, + { "COUPNUM", "COUPNUM", "com.sun.star.sheet.addin.Analysis.getCoupnum", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETCOUPNUM" }, + { "FVSCHEDULE", "FVSCHEDULE", "com.sun.star.sheet.addin.Analysis.getFvschedule", "COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETFVSCHEDULE" }, +}; + +size_t ScCompiler::GetAddInMapCount() +{ + return std::size(g_aAddInMap); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/optutil.cxx b/sc/source/core/tool/optutil.cxx new file mode 100644 index 0000000000..8f3e1fec9d --- /dev/null +++ b/sc/source/core/tool/optutil.cxx @@ -0,0 +1,67 @@ +/* -*- 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 <optutil.hxx> +#include <global.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/localedatawrapper.hxx> + +bool ScOptionsUtil::IsMetricSystem() +{ + if (utl::ConfigManager::IsFuzzing()) + return true; + + //TODO: which language should be used here - system language or installed office language? + + MeasurementSystem eSys = ScGlobal::getLocaleData().getMeasurementSystemEnum(); + + return ( eSys == MeasurementSystem::Metric ); +} + +ScLinkConfigItem::ScLinkConfigItem( const OUString& rSubTree ) : + ConfigItem( rSubTree ) +{ +} + +ScLinkConfigItem::ScLinkConfigItem( const OUString& rSubTree, ConfigItemMode nMode ) : + ConfigItem( rSubTree, nMode ) +{ +} + +void ScLinkConfigItem::SetCommitLink( const Link<ScLinkConfigItem&,void>& rLink ) +{ + aCommitLink = rLink; +} + +void ScLinkConfigItem::SetNotifyLink( const Link<ScLinkConfigItem&,void>& rLink ) +{ + aNotifyLink = rLink; +} + +void ScLinkConfigItem::Notify( const css::uno::Sequence<OUString>& /* aPropertyNames */ ) +{ + aNotifyLink.Call(*this); +} + +void ScLinkConfigItem::ImplCommit() +{ + aCommitLink.Call( *this ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/orcusxml.cxx b/sc/source/core/tool/orcusxml.cxx new file mode 100644 index 0000000000..eddeb977b9 --- /dev/null +++ b/sc/source/core/tool/orcusxml.cxx @@ -0,0 +1,31 @@ +/* -*- 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/. + */ + +#include <orcusxml.hxx> + +#include <utility> +#include <vcl/weld.hxx> + +ScOrcusXMLTreeParam::EntryData::EntryData(EntryType eType) + : mnNamespaceID(0) + , meType(eType) + , maLinkedPos(ScAddress::INITIALIZE_INVALID) + , mbRangeParent(false) + , mbLeafNode(true) +{} + +ScOrcusXMLTreeParam::EntryData* ScOrcusXMLTreeParam::getUserData(const weld::TreeView& rControl, const weld::TreeIter& rEntry) +{ + return weld::fromId<ScOrcusXMLTreeParam::EntryData*>(rControl.get_id(rEntry)); +} + +ScOrcusImportXMLParam::CellLink::CellLink(const ScAddress& rPos, OString aPath) : + maPos(rPos), maPath(std::move(aPath)) {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/parclass.cxx b/sc/source/core/tool/parclass.cxx new file mode 100644 index 0000000000..473177c8fc --- /dev/null +++ b/sc/source/core/tool/parclass.cxx @@ -0,0 +1,710 @@ +/* -*- 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 <parclass.hxx> +#include <global.hxx> +#include <callform.hxx> +#include <addincol.hxx> +#include <formula/token.hxx> +#include <unotools/charclass.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <string.h> + +#if DEBUG_SC_PARCLASSDOC +// the documentation thingy +#include <com/sun/star/sheet/FormulaLanguage.hpp> +#include <rtl/strbuf.hxx> +#include <formula/funcvarargs.h> +#include "compiler.hxx" +#endif + +using namespace formula; + +/* Following assumptions are made: + * - OpCodes not specified at all will have at least one and only parameters of + * type Value, no check is done on the count of parameters => no Bounds type + * is returned. + * - For OpCodes with a variable number of parameters the type(s) of the last + * repeated parameter(s) specified determine(s) the type(s) of all following + * parameters. + */ + +const ScParameterClassification::RawData ScParameterClassification::pRawData[] = +{ + // { OpCode, {{ ParamClass, ... }, nRepeatLast, ReturnClass }}, + + // IF() and CHOOSE() are somewhat special, since the ScJumpMatrix is + // created inside those functions and ConvertMatrixParameters() is not + // called for them. + { ocIf, {{ Array, Reference, Reference }, 0, Value }}, + { ocIfError, {{ Array, Reference }, 0, Value }}, + { ocIfNA, {{ Array, Reference }, 0, Value }}, + { ocChoose, {{ Array, Reference }, 1, Value }}, + // Other specials. + { ocArrayClose, {{ Bounds }, 0, Bounds }}, + { ocArrayColSep, {{ Bounds }, 0, Bounds }}, + { ocArrayOpen, {{ Bounds }, 0, Bounds }}, + { ocArrayRowSep, {{ Bounds }, 0, Bounds }}, + { ocBad, {{ Bounds }, 0, Bounds }}, + { ocClose, {{ Bounds }, 0, Bounds }}, + { ocColRowName, {{ Bounds }, 0, Value }}, // or Reference? + { ocColRowNameAuto, {{ Bounds }, 0, Value }}, // or Reference? + { ocDBArea, {{ Bounds }, 0, Value }}, // or Reference? + { ocMatRef, {{ Bounds }, 0, Value }}, + { ocMissing, {{ Bounds }, 0, Value }}, + { ocNoName, {{ Bounds }, 0, Bounds }}, + { ocOpen, {{ Bounds }, 0, Bounds }}, + { ocSep, {{ Bounds }, 0, Bounds }}, + { ocSkip, {{ Bounds }, 0, Bounds }}, + { ocSpaces, {{ Bounds }, 0, Bounds }}, + { ocStop, {{ Bounds }, 0, Bounds }}, + { ocStringXML, {{ Bounds }, 0, Bounds }}, + { ocTableRef, {{ Bounds }, 0, Value }}, // or Reference? + { ocTableRefClose, {{ Bounds }, 0, Bounds }}, + { ocTableRefItemAll, {{ Bounds }, 0, Bounds }}, + { ocTableRefItemData, {{ Bounds }, 0, Bounds }}, + { ocTableRefItemHeaders, {{ Bounds }, 0, Bounds }}, + { ocTableRefItemThisRow, {{ Bounds }, 0, Bounds }}, + { ocTableRefItemTotals, {{ Bounds }, 0, Bounds }}, + { ocTableRefOpen, {{ Bounds }, 0, Bounds }}, + // Error constants. + { ocErrDivZero, {{ Bounds }, 0, Bounds }}, + { ocErrNA, {{ Bounds }, 0, Bounds }}, + { ocErrName, {{ Bounds }, 0, Bounds }}, + { ocErrNull, {{ Bounds }, 0, Bounds }}, + { ocErrNum, {{ Bounds }, 0, Bounds }}, + { ocErrRef, {{ Bounds }, 0, Bounds }}, + { ocErrValue, {{ Bounds }, 0, Bounds }}, + // Functions with Value parameters only but not in resource. + { ocBackSolver, {{ Value, Value, Value }, 0, Value }}, + { ocTableOp, {{ Value, Value, Value, Value, Value }, 0, Value }}, + // Operators and functions. + { ocAdd, {{ Array, Array }, 0, Value }}, + { ocAggregate, {{ Value, Value, ReferenceOrForceArray }, 1, Value }}, + { ocAmpersand, {{ Array, Array }, 0, Value }}, + { ocAnd, {{ Reference }, 1, Value }}, + { ocAreas, {{ Reference }, 0, Value }}, + { ocAveDev, {{ Reference }, 1, Value }}, + { ocAverage, {{ ReferenceOrRefArray }, 1, Value }}, + { ocAverageA, {{ ReferenceOrRefArray }, 1, Value }}, + { ocAverageIf, {{ ReferenceOrRefArray, Value, Reference }, 0, Value }}, + { ocAverageIfs, {{ ReferenceOrRefArray, ReferenceOrRefArray, Value }, 2, Value }}, + { ocCell, {{ Value, Reference }, 0, Value }}, + { ocColumn, {{ Reference }, 0, Value }}, + { ocColumns, {{ Reference }, 1, Value }}, + { ocConcat_MS, {{ Reference }, 1, Value }}, + { ocCorrel, {{ ForceArray, ForceArray }, 0, Value }}, + { ocCount, {{ ReferenceOrRefArray }, 1, Value }}, + { ocCount2, {{ ReferenceOrRefArray }, 1, Value }}, + { ocCountEmptyCells, {{ ReferenceOrRefArray }, 0, Value }}, + { ocCountIf, {{ ReferenceOrRefArray, Value }, 0, Value }}, + { ocCountIfs, {{ ReferenceOrRefArray, Value }, 2, Value }}, + { ocCovar, {{ ForceArray, ForceArray }, 0, Value }}, + { ocCovarianceP, {{ ForceArray, ForceArray }, 0, Value }}, + { ocCovarianceS, {{ ForceArray, ForceArray }, 0, Value }}, + { ocCurrent, {{ Bounds }, 0, Value }}, + { ocDBAverage, {{ Reference, Reference, Reference }, 0, Value }}, + { ocDBCount, {{ Reference, Reference, Reference }, 0, Value }}, + { ocDBCount2, {{ Reference, Reference, Reference }, 0, Value }}, + { ocDBGet, {{ Reference, Reference, Reference }, 0, Value }}, + { ocDBMax, {{ Reference, Reference, Reference }, 0, Value }}, + { ocDBMin, {{ Reference, Reference, Reference }, 0, Value }}, + { ocDBProduct, {{ Reference, Reference, Reference }, 0, Value }}, + { ocDBStdDev, {{ Reference, Reference, Reference }, 0, Value }}, + { ocDBStdDevP, {{ Reference, Reference, Reference }, 0, Value }}, + { ocDBSum, {{ Reference, Reference, Reference }, 0, Value }}, + { ocDBVar, {{ Reference, Reference, Reference }, 0, Value }}, + { ocDBVarP, {{ Reference, Reference, Reference }, 0, Value }}, + { ocDevSq, {{ Reference }, 1, Value }}, + { ocDiv, {{ Array, Array }, 0, Value }}, + { ocEqual, {{ Array, Array }, 0, Value }}, + { ocFTest, {{ ForceArray, ForceArray }, 0, Value }}, + { ocFalse, {{ Bounds }, 0, Value }}, + { ocForecast, {{ Value, ForceArray, ForceArray }, 0, Value }}, + { ocForecast_ETS_ADD, {{ ForceArray, ForceArray, ForceArray, Value, Value, Value }, 0, Value }}, + { ocForecast_ETS_MUL, {{ ForceArray, ForceArray, ForceArray, Value, Value, Value }, 0, Value }}, + { ocForecast_ETS_PIA, {{ ForceArray, ForceArray, ForceArray, Value, Value, Value, Value }, 0, Value }}, + { ocForecast_ETS_PIM, {{ ForceArray, ForceArray, ForceArray, Value, Value, Value, Value }, 0, Value }}, + { ocForecast_ETS_SEA, {{ ForceArray, ForceArray, Value, Value }, 0, Value }}, + { ocForecast_ETS_STA, {{ ForceArray, ForceArray, ForceArray, Value, Value, Value }, 0, Value }}, + { ocForecast_ETS_STM, {{ ForceArray, ForceArray, ForceArray, Value, Value, Value }, 0, Value }}, + { ocFormula, {{ Reference }, 0, Value }}, + { ocFourier, {{ ForceArray, Value, Value, Value, Value }, 0, Value }}, + { ocFrequency, {{ ReferenceOrForceArray, ReferenceOrForceArray }, 0, ForceArrayReturn }}, + { ocGCD, {{ Reference }, 1, Value }}, + { ocGeoMean, {{ Reference }, 1, Value }}, + { ocGetActDate, {{ Bounds }, 0, Value }}, + { ocGetActTime, {{ Bounds }, 0, Value }}, + { ocGreater, {{ Array, Array }, 0, Value }}, + { ocGreaterEqual, {{ Array, Array }, 0, Value }}, + { ocGrowth, {{ Reference, Reference, Reference, Value }, 0, Value }}, + { ocHLookup, {{ Value, ReferenceOrForceArray, Value, Value }, 0, Value }}, + { ocHarMean, {{ Reference }, 1, Value }}, + { ocIRR, {{ Reference, Value }, 0, Value }}, + { ocIndex, {{ Reference, Value, Value, Value }, 0, Value }}, + { ocIndirect, {{ Value, Value }, 0, Reference }}, + { ocIntercept, {{ ForceArray, ForceArray }, 0, Value }}, + { ocIntersect, {{ Reference, Reference }, 0, Reference }}, + { ocIsFormula, {{ Reference }, 0, Value }}, + { ocIsRef, {{ Reference }, 0, Value }}, + { ocKurt, {{ Reference }, 1, Value }}, + { ocLCM, {{ Reference }, 1, Value }}, + { ocLarge, {{ Reference, Value }, 0, Value }}, + { ocLess, {{ Array, Array }, 0, Value }}, + { ocLessEqual, {{ Array, Array }, 0, Value }}, + { ocLinest, {{ ForceArray, ForceArray, Value, Value }, 0, Value }}, + { ocLogest, {{ ForceArray, ForceArray, Value, Value }, 0, Value }}, + { ocLookup, {{ Value, ReferenceOrForceArray, ReferenceOrForceArray }, 0, Value }}, + { ocMIRR, {{ Reference, Value, Value }, 0, Value }}, + { ocMatDet, {{ ForceArray }, 0, Value }}, + { ocMatInv, {{ ForceArray }, 0, Value }}, + { ocMatMult, {{ ForceArray, ForceArray }, 0, Value }}, + { ocMatTrans, {{ ForceArray }, 0, ForceArrayReturn }}, + { ocMatValue, {{ Reference, Value, Value }, 0, Value }}, + { ocMatch, {{ Value, ReferenceOrForceArray, Value }, 0, Value }}, + { ocMax, {{ ReferenceOrRefArray }, 1, Value }}, + { ocMaxA, {{ ReferenceOrRefArray }, 1, Value }}, + { ocMaxIfs_MS, {{ ReferenceOrRefArray, ReferenceOrRefArray, Value }, 2, Value }}, + { ocMedian, {{ Reference }, 1, Value }}, + { ocMin, {{ ReferenceOrRefArray }, 1, Value }}, + { ocMinA, {{ ReferenceOrRefArray }, 1, Value }}, + { ocMinIfs_MS, {{ ReferenceOrRefArray, ReferenceOrRefArray, Value }, 2, Value }}, + { ocModalValue, {{ ForceArray }, 1, Value }}, + { ocModalValue_MS, {{ ForceArray }, 1, Value }}, + { ocModalValue_Multi,{{ ForceArray }, 1, Value }}, + { ocMul, {{ Array, Array }, 0, Value }}, + { ocMultiArea, {{ Reference }, 1, Reference }}, + { ocNPV, {{ Value, Reference }, 1, Value }}, + { ocNeg, {{ Array }, 0, Value }}, + { ocNegSub, {{ Array }, 0, Value }}, + { ocNetWorkdays, {{ Value, Value, Reference, Reference }, 0, Value }}, + { ocNetWorkdays_MS, {{ Value, Value, Value, Reference }, 0, Value }}, + { ocNot, {{ Array }, 0, Value }}, + { ocNotAvail, {{ Bounds }, 0, Value }}, + { ocNotEqual, {{ Array, Array }, 0, Value }}, + { ocOffset, {{ Reference, Value, Value, Value, Value }, 0, Reference }}, + { ocOr, {{ Reference }, 1, Value }}, + { ocPearson, {{ ForceArray, ForceArray }, 0, Value }}, + { ocPercentSign, {{ Array }, 0, Value }}, + { ocPercentile, {{ Reference, Value }, 0, Value }}, + { ocPercentile_Exc, {{ Reference, Value }, 0, Value }}, + { ocPercentile_Inc, {{ Reference, Value }, 0, Value }}, + { ocPercentrank, {{ Reference, Value, Value }, 0, Value }}, + { ocPercentrank_Exc, {{ Reference, Value, Value }, 0, Value }}, + { ocPercentrank_Inc, {{ Reference, Value, Value }, 0, Value }}, + { ocPi, {{ Bounds }, 0, Value }}, + { ocPow, {{ Array, Array }, 0, Value }}, + { ocPower, {{ Array, Array }, 0, Value }}, + { ocProb, {{ ForceArray, ForceArray, Value, Value }, 0, Value }}, + { ocProduct, {{ ReferenceOrRefArray }, 1, Value }}, + { ocQuartile, {{ Reference, Value }, 0, Value }}, + { ocQuartile_Exc, {{ Reference, Value }, 0, Value }}, + { ocQuartile_Inc, {{ Reference, Value }, 0, Value }}, + { ocRSQ, {{ ForceArray, ForceArray }, 0, Value }}, + { ocRandom, {{ Bounds }, 0, Value }}, + { ocRandomNV, {{ Bounds }, 0, Value }}, + { ocRange, {{ Reference, Reference }, 0, Reference }}, + { ocRank, {{ Value, Reference, Value }, 0, Value }}, + { ocRank_Avg, {{ Value, Reference, Value }, 0, Value }}, + { ocRank_Eq, {{ Value, Reference, Value }, 0, Value }}, + { ocRow, {{ Reference }, 0, Value }}, + { ocRows, {{ Reference }, 1, Value }}, + { ocSTEYX, {{ ForceArray, ForceArray }, 0, Value }}, + { ocSheet, {{ Reference }, 0, Value }}, + { ocSheets, {{ Reference }, 1, Value }}, + { ocSkew, {{ Reference }, 1, Value }}, + { ocSkewp, {{ Reference }, 1, Value }}, + { ocSlope, {{ ForceArray, ForceArray }, 0, Value }}, + { ocSmall, {{ Reference, Value }, 0, Value }}, + { ocStDev, {{ Reference }, 1, Value }}, + { ocStDevA, {{ Reference }, 1, Value }}, + { ocStDevP, {{ Reference }, 1, Value }}, + { ocStDevPA, {{ Reference }, 1, Value }}, + { ocStDevP_MS, {{ Reference }, 1, Value }}, + { ocStDevS, {{ Reference }, 1, Value }}, + { ocSub, {{ Array, Array }, 0, Value }}, + { ocSubTotal, {{ Value, ReferenceOrRefArray }, 1, Value }}, + { ocSum, {{ ReferenceOrRefArray }, 1, Value }}, + { ocSumIf, {{ ReferenceOrRefArray, Value, Reference }, 0, Value }}, + { ocSumIfs, {{ ReferenceOrRefArray, ReferenceOrRefArray, Value }, 2, Value }}, + { ocSumProduct, {{ ForceArray }, 1, Value }}, + { ocSumSQ, {{ ReferenceOrRefArray }, 1, Value }}, + { ocSumX2DY2, {{ ForceArray, ForceArray }, 0, Value }}, + { ocSumX2MY2, {{ ForceArray, ForceArray }, 0, Value }}, + { ocSumXMY2, {{ ForceArray, ForceArray }, 0, Value }}, + { ocTTest, {{ ForceArray, ForceArray, Value, Value }, 0, Value }}, + { ocTextJoin_MS, {{ Reference, Value, Reference }, 1, Value }}, + { ocTrend, {{ Reference, Reference, Reference, Value }, 0, Value }}, + { ocTrimMean, {{ Reference, Value }, 0, Value }}, + { ocTrue, {{ Bounds }, 0, Value }}, + { ocUnion, {{ Reference, Reference }, 0, Reference }}, + { ocVLookup, {{ Value, ReferenceOrForceArray, Value, Value }, 0, Value }}, + { ocVar, {{ ReferenceOrRefArray }, 1, Value }}, + { ocVarA, {{ ReferenceOrRefArray }, 1, Value }}, + { ocVarP, {{ ReferenceOrRefArray }, 1, Value }}, + { ocVarPA, {{ ReferenceOrRefArray }, 1, Value }}, + { ocVarP_MS, {{ Reference }, 1, Value }}, + { ocVarS, {{ Reference }, 1, Value }}, + { ocWhitespace, {{ Bounds }, 0, Bounds }}, + { ocWorkday_MS, {{ Value, Value, Value, Reference }, 0, Value }}, + { ocXor, {{ Reference }, 1, Value }}, + { ocZTest, {{ Reference, Value, Value }, 0, Value }}, + { ocZTest_MS, {{ Reference, Value, Value }, 0, Value }}, + // Excel doubts: + // ocN, ocT: Excel says (and handles) Reference, error? This means no + // position dependent SingleRef if DoubleRef, and no array calculation, + // just the upper left corner. We never did that for ocT and now also not + // for ocN (position dependent intersection worked before but array + // didn't). No specifics in ODFF, so the general rule applies. Gnumeric + // does the same. + { ocN, {{ Value }, 0, Value }}, + { ocT, {{ Value }, 0, Value }}, + // The stopper. + { ocNone, {{ Bounds }, 0, Value }} +}; + +ScParameterClassification::RunData * ScParameterClassification::pData = nullptr; + +void ScParameterClassification::Init() +{ + if ( pData ) + return; + pData = new RunData[ SC_OPCODE_LAST_OPCODE_ID + 1 ]; + memset( pData, 0, sizeof(RunData) * (SC_OPCODE_LAST_OPCODE_ID + 1)); + + // init from specified static data above + for (const auto & i : pRawData) + { + const RawData* pRaw = &i; + if ( pRaw->eOp > SC_OPCODE_LAST_OPCODE_ID ) + { + OSL_ENSURE( pRaw->eOp == ocNone, "RawData OpCode error"); + } + else + { + RunData* pRun = &pData[ pRaw->eOp ]; + SAL_WARN_IF(pRun->aData.nParam[0] != Unknown, "sc.core", "already assigned: " << static_cast<int>(pRaw->eOp)); + memcpy( &(pRun->aData), &(pRaw->aData), sizeof(CommonData)); + // fill 0-initialized fields with real values + if ( pRun->aData.nRepeatLast ) + { + for ( sal_Int32 j=0; j < CommonData::nMaxParams; ++j ) + { + if ( pRun->aData.nParam[j] ) + pRun->nMinParams = sal::static_int_cast<sal_uInt8>( j+1 ); + else if (j >= pRun->aData.nRepeatLast) + pRun->aData.nParam[j] = pRun->aData.nParam[j - pRun->aData.nRepeatLast]; + else + { + SAL_INFO( + "sc.core", + "bad classification: eOp " << +pRaw->eOp + << ", repeated param " << j + << " negative offset"); + pRun->aData.nParam[j] = Unknown; + } + } + } + else + { + for ( sal_Int32 j=0; j < CommonData::nMaxParams; ++j ) + { + if ( !pRun->aData.nParam[j] ) + { + if ( j == 0 || pRun->aData.nParam[j-1] != Bounds ) + pRun->nMinParams = sal::static_int_cast<sal_uInt8>( j ); + pRun->aData.nParam[j] = Bounds; + } + } + if ( !pRun->nMinParams && + pRun->aData.nParam[CommonData::nMaxParams-1] != Bounds) + pRun->nMinParams = CommonData::nMaxParams; + } + for (const formula::ParamClass & j : pRun->aData.nParam) + { + if ( j == ForceArray || j == ReferenceOrForceArray ) + { + pRun->bHasForceArray = true; + break; // for + } + } + } + } + +#if DEBUG_SC_PARCLASSDOC + GenerateDocumentation(); +#endif +} + +void ScParameterClassification::Exit() +{ + delete [] pData; + pData = nullptr; +} + +formula::ParamClass ScParameterClassification::GetParameterType( + const formula::FormulaToken* pToken, sal_uInt16 nParameter) +{ + OpCode eOp = pToken->GetOpCode(); + switch ( eOp ) + { + case ocExternal: + return GetExternalParameterType( pToken, nParameter); + case ocMacro: + return (nParameter == SAL_MAX_UINT16 ? Value : Reference); + default: + { + // added to avoid warnings + } + } + if ( 0 <= static_cast<short>(eOp) && eOp <= SC_OPCODE_LAST_OPCODE_ID ) + { + sal_uInt8 nRepeat; + formula::ParamClass eType; + if (nParameter == SAL_MAX_UINT16) + eType = pData[eOp].aData.eReturn; + else if ( nParameter < CommonData::nMaxParams ) + eType = pData[eOp].aData.nParam[nParameter]; + else if ( (nRepeat = pData[eOp].aData.nRepeatLast) > 0 ) + { + // The usual case is 1 repeated parameter, we don't need to + // calculate that on each call. + sal_uInt16 nParam = (nRepeat > 1 ? + (pData[eOp].nMinParams - + ((nParameter - pData[eOp].nMinParams) % nRepeat)) : + pData[eOp].nMinParams); + return pData[eOp].aData.nParam[nParam]; + } + else + eType = Bounds; + return eType == Unknown ? Value : eType; + } + return Unknown; +} + +formula::ParamClass ScParameterClassification::GetExternalParameterType( const formula::FormulaToken* pToken, + sal_uInt16 nParameter) +{ + formula::ParamClass eRet = Unknown; + if (nParameter == SAL_MAX_UINT16) + return eRet; + + // similar to ScInterpreter::ScExternal() + OUString aFuncName = pToken->GetExternal().toAsciiUpperCase(); // programmatic name + { + const LegacyFuncData* pLegacyFuncData = ScGlobal::GetLegacyFuncCollection()->findByName(aFuncName); + if (pLegacyFuncData) + { + if ( nParameter >= pLegacyFuncData->GetParamCount() ) + eRet = Bounds; + else + { + switch ( pLegacyFuncData->GetParamType( nParameter) ) + { + case ParamType::PTR_DOUBLE: + case ParamType::PTR_STRING: + eRet = Value; + break; + default: + eRet = Reference; + // also array types are created using an area reference + } + } + return eRet; + } + } + + OUString aUnoName = + ScGlobal::GetAddInCollection()->FindFunction(aFuncName, false); + + if (!aUnoName.isEmpty()) + { + // the relevant parts of ScUnoAddInCall without having to create one + const ScUnoAddInFuncData* pFuncData = + ScGlobal::GetAddInCollection()->GetFuncData( aUnoName, true ); // need fully initialized data + if ( pFuncData ) + { + tools::Long nCount = pFuncData->GetArgumentCount(); + if ( nCount <= 0 ) + eRet = Bounds; + else + { + const ScAddInArgDesc* pArgs = pFuncData->GetArguments(); + if ( nParameter >= nCount && + pArgs[nCount-1].eType == SC_ADDINARG_VARARGS ) + eRet = Value; + // last arg is sequence, optional "any"s, we simply can't + // determine the type + if ( eRet == Unknown ) + { + if ( nParameter >= nCount ) + eRet = Bounds; + else + { + switch ( pArgs[nParameter].eType ) + { + case SC_ADDINARG_INTEGER: + case SC_ADDINARG_DOUBLE: + case SC_ADDINARG_STRING: + eRet = Value; + break; + default: + eRet = Reference; + } + } + } + } + } + } + return eRet; +} + +#if DEBUG_SC_PARCLASSDOC + +// add remaining functions, all Value parameters +void ScParameterClassification::MergeArgumentsFromFunctionResource() +{ + ScFunctionList* pFuncList = ScGlobal::GetStarCalcFunctionList(); + for ( const ScFuncDesc* pDesc = pFuncList->First(); pDesc; + pDesc = pFuncList->Next() ) + { + if ( pDesc->nFIndex > SC_OPCODE_LAST_OPCODE_ID || + pData[pDesc->nFIndex].aData.nParam[0] != Unknown ) + continue; // not an internal opcode or already done + + RunData* pRun = &pData[ pDesc->nFIndex ]; + sal_uInt16 nArgs = pDesc->GetSuppressedArgCount(); + if ( nArgs >= PAIRED_VAR_ARGS ) + { + nArgs -= PAIRED_VAR_ARGS - 2; + pRun->aData.nRepeatLast = 2; + } + else if ( nArgs >= VAR_ARGS ) + { + nArgs -= VAR_ARGS - 1; + pRun->aData.nRepeatLast = 1; + } + if ( nArgs > CommonData::nMaxParams ) + { + SAL_WARN( "sc", "ScParameterClassification::Init: too many arguments in listed function: " + << *(pDesc->pFuncName) + << ": " << nArgs ); + nArgs = CommonData::nMaxParams - 1; + pRun->aData.nRepeatLast = 1; + } + pRun->nMinParams = static_cast< sal_uInt8 >( nArgs ); + for ( sal_Int32 j=0; j < nArgs; ++j ) + { + pRun->aData.nParam[j] = Value; + } + if ( pRun->aData.nRepeatLast ) + { + for ( sal_Int32 j = nArgs; j < CommonData::nMaxParams; ++j ) + { + pRun->aData.nParam[j] = Value; + } + } + else + { + for ( sal_Int32 j = nArgs; j < CommonData::nMaxParams; ++j ) + { + pRun->aData.nParam[j] = Bounds; + } + } + } +} + +void ScParameterClassification::GenerateDocumentation() +{ + static const char aEnvVarName[] = "OOO_CALC_GENPARCLASSDOC"; + if ( !getenv( aEnvVarName) ) + return; + MergeArgumentsFromFunctionResource(); + ScAddress aAddress; + ScCompiler aComp(NULL,aAddress); + ScCompiler::OpCodeMapPtr xMap( aComp.GetOpCodeMap(css::sheet::FormulaLanguage::ENGLISH)); + if (!xMap) + return; + fflush( stderr); + size_t nCount = xMap->getSymbolCount(); + for ( size_t i=0; i<nCount; ++i ) + { + OpCode eOp = OpCode(i); + if ( !xMap->getSymbol(eOp).isEmpty() ) + { + OUStringBuffer aStr(xMap->getSymbol(eOp)); + formula::FormulaByteToken aToken( eOp); + sal_uInt8 nParams = GetMinimumParameters( eOp); + // preset parameter count according to opcode value, with some + // special handling + bool bAddParentheses = true; + if ( eOp < SC_OPCODE_STOP_DIV ) + { + bAddParentheses = false; // will be overridden below if parameters + switch ( eOp ) + { + case ocIf: + aToken.SetByte(3); + break; + case ocIfError: + case ocIfNA: + case ocChoose: + aToken.SetByte(2); + break; + case ocPercentSign: + aToken.SetByte(1); + break; + default:; + } + } + else if ( eOp < SC_OPCODE_STOP_ERRORS ) + { + bAddParentheses = false; + aToken.SetByte(0); + } + else if ( eOp < SC_OPCODE_STOP_BIN_OP ) + { + switch ( eOp ) + { + case ocAnd: + case ocOr: + aToken.SetByte(1); // (r1)AND(r2) --> AND( r1, ...) + break; + default: + aToken.SetByte(2); + } + } + else if ( eOp < SC_OPCODE_STOP_UN_OP ) + aToken.SetByte(1); + else if ( eOp < SC_OPCODE_STOP_NO_PAR ) + aToken.SetByte(0); + else if ( eOp < SC_OPCODE_STOP_1_PAR ) + aToken.SetByte(1); + else + aToken.SetByte( nParams); + // compare (this is a mere test for opcode order Div, BinOp, UnOp, + // NoPar, 1Par, ...) and override parameter count with + // classification + if ( nParams != aToken.GetByte() ) + SAL_WARN("sc.core", "(parameter count differs, token Byte: " << (int)aToken.GetByte() << " classification: " << (int)nParams << ") "); + aToken.SetByte( nParams); + if ( nParams != aToken.GetParamCount() ) + SAL_WARN("sc.core", "(parameter count differs, token ParamCount: " << (int)aToken.GetParamCount() << " classification: " << (int)nParams << ") "); + if (aToken.GetByte()) + bAddParentheses = true; + if (bAddParentheses) + aStr.append('('); + for ( sal_uInt16 j=0; j < nParams; ++j ) + { + if ( j > 0 ) + aStr.append(','); + formula::ParamClass eType = GetParameterType( &aToken, j); + switch ( eType ) + { + case Value : + aStr.append(" Value"); + break; + case Reference : + aStr.append(" Reference"); + break; + case ReferenceOrRefArray : + aStr.append(" ReferenceOrRefArray"); + break; + case Array : + aStr.append(" Array"); + break; + case ForceArray : + aStr.append(" ForceArray"); + break; + case ReferenceOrForceArray : + aStr.append(" ReferenceOrForceArray"); + break; + case Bounds : + aStr.append(" (Bounds, classification error?)"); + break; + default: + aStr.append(" (???, classification error?)"); + } + } + if ( HasRepeatParameters( eOp) ) + aStr.append(", ..."); + if ( nParams ) + aStr.append(' '); + if (bAddParentheses) + aStr.append(')'); + switch ( eOp ) + { + case ocRRI: + aStr.append(" // RRI in English resource, but ZGZ in English-only section"); + break; + case ocMultiArea: + aStr.append(" // e.g. combined first parameter of INDEX() function, not a real function"); + break; + case ocBackSolver: + aStr.append(" // goal seek via menu, not a real function"); + break; + case ocTableOp: + aStr.append(" // MULTIPLE.OPERATIONS in English resource, but TABLE in English-only section"); + break; + case ocNoName: + aStr.append(" // error function, not a real function"); + break; + default:; + } + // Return type. + formula::ParamClass eType = GetParameterType( &aToken, SAL_MAX_UINT16); + switch ( eType ) + { + case Value : + aStr.append(" -> Value"); + break; + case Reference : + aStr.append(" -> Reference"); + break; + case ReferenceOrRefArray : + aStr.append(" -> ReferenceOrRefArray"); + break; + case Array : + aStr.append(" -> Array"); + break; + case ForceArray : + aStr.append(" -> ForceArray"); + break; + case ReferenceOrForceArray : + aStr.append(" -> ReferenceOrForceArray"); + break; + case Bounds : + ; // nothing + break; + default: + aStr.append(" (-> ???, classification error?)"); + } + /* We could add yet another log domain for this, if we wanted... but + * as it more seldom than rarely used it's not actually necessary, + * just grep output. */ + SAL_INFO( "sc.core", "CALC_GENPARCLASSDOC: " << aStr.makeStringAndClear()); + } + } + fflush( stdout); +} + +#endif // OSL_DEBUG_LEVEL + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/printopt.cxx b/sc/source/core/tool/printopt.cxx new file mode 100644 index 0000000000..e9b3e15161 --- /dev/null +++ b/sc/source/core/tool/printopt.cxx @@ -0,0 +1,131 @@ +/* -*- 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 <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <osl/diagnose.h> + +#include <printopt.hxx> +#include <sc.hrc> + +using namespace utl; +using namespace com::sun::star::uno; + + +ScPrintOptions::ScPrintOptions() +{ + SetDefaults(); +} + +void ScPrintOptions::SetDefaults() +{ + bSkipEmpty = true; + bAllSheets = false; + bForceBreaks = false; +} + +bool ScPrintOptions::operator==( const ScPrintOptions& rOpt ) const +{ + return bSkipEmpty == rOpt.bSkipEmpty + && bAllSheets == rOpt.bAllSheets + && bForceBreaks == rOpt.bForceBreaks; +} + +ScTpPrintItem::ScTpPrintItem( const ScPrintOptions& rOpt ) : + SfxPoolItem ( SID_SCPRINTOPTIONS ), + theOptions ( rOpt ) +{ +} + +ScTpPrintItem::~ScTpPrintItem() +{ +} + +bool ScTpPrintItem::operator==( const SfxPoolItem& rItem ) const +{ + assert(SfxPoolItem::operator==(rItem)); + + const ScTpPrintItem& rPItem = static_cast<const ScTpPrintItem&>(rItem); + return ( theOptions == rPItem.theOptions ); +} + +ScTpPrintItem* ScTpPrintItem::Clone( SfxItemPool * ) const +{ + return new ScTpPrintItem( *this ); +} + +constexpr OUStringLiteral CFGPATH_PRINT = u"Office.Calc/Print"; + +#define SCPRINTOPT_EMPTYPAGES 0 +#define SCPRINTOPT_ALLSHEETS 1 +#define SCPRINTOPT_FORCEBREAKS 2 + +Sequence<OUString> ScPrintCfg::GetPropertyNames() +{ + return {"Page/EmptyPages", // SCPRINTOPT_EMPTYPAGES + "Other/AllSheets", // SCPRINTOPT_ALLSHEETS + "Page/ForceBreaks"}; // SCPRINTOPT_FORCEBREAKS; +} + +ScPrintCfg::ScPrintCfg() : + ConfigItem( CFGPATH_PRINT ) +{ + Sequence<OUString> aNames = GetPropertyNames(); + EnableNotification(aNames); + ReadCfg(); +} + +void ScPrintCfg::ReadCfg() +{ + const Sequence<OUString> aNames = GetPropertyNames(); + const Sequence<Any> aValues = GetProperties(aNames); + OSL_ENSURE(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if(aValues.getLength() != aNames.getLength()) + return; + + if (bool bVal; aValues[SCPRINTOPT_EMPTYPAGES] >>= bVal) + SetSkipEmpty(!bVal); // reversed + if (bool bVal; aValues[SCPRINTOPT_ALLSHEETS] >>= bVal) + SetAllSheets(bVal); + if (bool bVal; aValues[SCPRINTOPT_FORCEBREAKS] >>= bVal) + SetForceBreaks(bVal); +} + +void ScPrintCfg::ImplCommit() +{ + Sequence<OUString> aNames = GetPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + pValues[SCPRINTOPT_EMPTYPAGES] <<= !GetSkipEmpty(); // reversed + pValues[SCPRINTOPT_ALLSHEETS] <<= GetAllSheets(); + pValues[SCPRINTOPT_FORCEBREAKS] <<= GetForceBreaks(); + PutProperties(aNames, aValues); +} + +void ScPrintCfg::SetOptions( const ScPrintOptions& rNew ) +{ + *static_cast<ScPrintOptions*>(this) = rNew; + SetModified(); + Commit(); +} + +void ScPrintCfg::Notify( const css::uno::Sequence< OUString >& ) { ReadCfg(); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/prnsave.cxx b/sc/source/core/tool/prnsave.cxx new file mode 100644 index 0000000000..abf4926001 --- /dev/null +++ b/sc/source/core/tool/prnsave.cxx @@ -0,0 +1,127 @@ +/* -*- 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 <prnsave.hxx> +#include <address.hxx> + +#include <osl/diagnose.h> +#include <tools/json_writer.hxx> + +// Data per table + +ScPrintSaverTab::ScPrintSaverTab() : + mbEntireSheet(false) +{ +} + +ScPrintSaverTab::~ScPrintSaverTab() +{ +} + +void ScPrintSaverTab::SetAreas( ScRangeVec&& rRanges, bool bEntireSheet ) +{ + maPrintRanges = std::move(rRanges); + mbEntireSheet = bEntireSheet; +} + +void ScPrintSaverTab::SetRepeat( std::optional<ScRange> oCol, std::optional<ScRange> oRow ) +{ + moRepeatCol = std::move(oCol); + moRepeatRow = std::move(oRow); +} + +bool ScPrintSaverTab::operator==( const ScPrintSaverTab& rCmp ) const +{ + return + (moRepeatCol == rCmp.moRepeatCol) && + (moRepeatRow == rCmp.moRepeatRow) && + (mbEntireSheet == rCmp.mbEntireSheet) && + (maPrintRanges == rCmp.maPrintRanges); +} + +// Data for the whole document + +ScPrintRangeSaver::ScPrintRangeSaver( SCTAB nCount ) : + nTabCount( nCount ) +{ + if (nCount > 0) + pData.reset( new ScPrintSaverTab[nCount] ); +} + +ScPrintRangeSaver::~ScPrintRangeSaver() +{ +} + +ScPrintSaverTab& ScPrintRangeSaver::GetTabData(SCTAB nTab) +{ + OSL_ENSURE(nTab<nTabCount,"ScPrintRangeSaver Tab too big"); + return pData[nTab]; +} + +const ScPrintSaverTab& ScPrintRangeSaver::GetTabData(SCTAB nTab) const +{ + OSL_ENSURE(nTab<nTabCount,"ScPrintRangeSaver Tab too big"); + return pData[nTab]; +} + +void ScPrintRangeSaver::GetPrintRangesInfo(tools::JsonWriter& rPrintRanges) const +{ + // Array for sheets in the document. + auto printRanges = rPrintRanges.startArray("printranges"); + for (SCTAB nTab = 0; nTab < nTabCount; nTab++) + { + auto sheetNode = rPrintRanges.startStruct(); + const ScPrintSaverTab& rPsTab = pData[nTab]; + const std::vector<ScRange>& rRangeVec = rPsTab.GetPrintRanges(); + + rPrintRanges.put("sheet", static_cast<sal_Int32>(nTab)); + + // Array for ranges within each sheet. + auto sheetRanges = rPrintRanges.startArray("ranges"); + OStringBuffer aRanges; + sal_Int32 nLast = rRangeVec.size() - 1; + for (sal_Int32 nIdx = 0; nIdx <= nLast; ++nIdx) + { + const ScRange& rRange = rRangeVec[nIdx]; + aRanges.append("[ " + + OString::number(rRange.aStart.Col()) + ", " + + OString::number(rRange.aStart.Row()) + ", " + + OString::number(rRange.aEnd.Col()) + ", " + + OString::number(rRange.aEnd.Row()) + + (nLast == nIdx ? std::string_view("]") : std::string_view("], "))); + } + + rPrintRanges.putRaw(aRanges); + } +} + +bool ScPrintRangeSaver::operator==( const ScPrintRangeSaver& rCmp ) const +{ + bool bEqual = ( nTabCount == rCmp.nTabCount ); + if (bEqual) + for (SCTAB i=0; i<nTabCount; i++) + if (!(pData[i]==rCmp.pData[i])) + { + bEqual = false; + break; + } + return bEqual; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/progress.cxx b/sc/source/core/tool/progress.cxx new file mode 100644 index 0000000000..ae0b99b32f --- /dev/null +++ b/sc/source/core/tool/progress.cxx @@ -0,0 +1,178 @@ +/* -*- 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 <sfx2/app.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/progress.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/sfxsids.hrc> +#include <svl/eitem.hxx> +#include <svl/itemset.hxx> +#include <osl/diagnose.h> + +#include <com/sun/star/frame/XModel.hpp> + +#define SC_PROGRESS_CXX +#include <progress.hxx> +#include <document.hxx> +#include <docsh.hxx> +#include <globstr.hrc> +#include <scresid.hxx> + +using namespace com::sun::star; + +static ScProgress theDummyInterpretProgress; +SfxProgress* ScProgress::pGlobalProgress = nullptr; +sal_uInt64 ScProgress::nGlobalRange = 0; +sal_uInt64 ScProgress::nGlobalPercent = 0; +ScProgress* ScProgress::pInterpretProgress = &theDummyInterpretProgress; +sal_uInt64 ScProgress::nInterpretProgress = 0; +ScDocument* ScProgress::pInterpretDoc; +bool ScProgress::bIdleWasEnabled = false; + +static bool lcl_IsHiddenDocument( const SfxObjectShell* pObjSh ) +{ + if (pObjSh) + { + SfxMedium* pMed = pObjSh->GetMedium(); + if (pMed) + { + if (const SfxBoolItem* pItem = pMed->GetItemSet().GetItemIfSet(SID_HIDDEN); + pItem && pItem->GetValue()) + return true; + } + } + return false; +} + +static bool lcl_HasControllersLocked( const SfxObjectShell& rObjSh ) +{ + uno::Reference<frame::XModel> xModel( rObjSh.GetBaseModel() ); + if (xModel.is()) + return xModel->hasControllersLocked(); + return false; +} + +ScProgress::ScProgress(SfxObjectShell* pObjSh, const OUString& rText, + sal_uInt64 nRange, bool bWait) + : bEnabled(true) +{ + + if ( pGlobalProgress || SfxProgress::GetActiveProgress() ) + { + if ( lcl_IsHiddenDocument(pObjSh) ) + { + // loading a hidden document while a progress is active is possible - no error + pProgress = nullptr; + } + else + { + OSL_FAIL( "ScProgress: there can be only one!" ); + pProgress = nullptr; + } + } + else if ( SfxGetpApp()->IsDowning() ) + { + // This happens. E.g. when saving the clipboard-content as OLE when closing the app. + // In this case a SfxProgress would produce dirt in memory. + //TODO: Should that be this way ??? + + pProgress = nullptr; + } + else if ( pObjSh && ( pObjSh->GetCreateMode() == SfxObjectCreateMode::EMBEDDED || + pObjSh->GetProgress() || + lcl_HasControllersLocked(*pObjSh) ) ) + { + // no own progress for embedded objects, + // no second progress if the document already has one + + pProgress = nullptr; + } + else + { + pProgress.reset(new SfxProgress( pObjSh, rText, nRange, bWait )); + pGlobalProgress = pProgress.get(); + nGlobalRange = nRange; + nGlobalPercent = 0; + } +} + +ScProgress::ScProgress() + : bEnabled(true) +{ + // DummyInterpret +} + +ScProgress::~ScProgress() +{ + if ( pProgress ) + { + pProgress.reset(); + pGlobalProgress = nullptr; + nGlobalRange = 0; + nGlobalPercent = 0; + } +} + +void ScProgress::CreateInterpretProgress( ScDocument* pDoc, bool bWait ) +{ + if ( nInterpretProgress ) + nInterpretProgress++; + else if ( pDoc->GetAutoCalc() ) + { + nInterpretProgress = 1; + bIdleWasEnabled = pDoc->IsIdleEnabled(); + pDoc->EnableIdle(false); + // Interpreter may be called in many circumstances, also if another + // progress bar is active, for example while adapting row heights. + // Keep the dummy interpret progress. + if ( !pGlobalProgress ) + pInterpretProgress = new ScProgress( pDoc->GetDocumentShell(), + ScResId( STR_PROGRESS_CALCULATING ), + pDoc->GetFormulaCodeInTree()/MIN_NO_CODES_PER_PROGRESS_UPDATE, bWait ); + pInterpretDoc = pDoc; + } +} + +void ScProgress::DeleteInterpretProgress() +{ + if ( !nInterpretProgress ) + return; + + /* Do not decrement 'nInterpretProgress', before 'pInterpretProgress' + is deleted. In rare cases, deletion of 'pInterpretProgress' causes + a refresh of the sheet window which may call CreateInterpretProgress + and DeleteInterpretProgress again (from Output::DrawStrings), + resulting in double deletion of 'pInterpretProgress'. */ + if ( nInterpretProgress == 1 ) + { + if ( pInterpretProgress != &theDummyInterpretProgress ) + { + // move pointer to local temporary to avoid double deletion + ScProgress* pTmpProgress = pInterpretProgress; + pInterpretProgress = &theDummyInterpretProgress; + delete pTmpProgress; + } + if ( pInterpretDoc ) + pInterpretDoc->EnableIdle(bIdleWasEnabled); + } + --nInterpretProgress; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/queryentry.cxx b/sc/source/core/tool/queryentry.cxx new file mode 100644 index 0000000000..d66382d2e2 --- /dev/null +++ b/sc/source/core/tool/queryentry.cxx @@ -0,0 +1,207 @@ +/* -*- 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 <queryentry.hxx> + +#include <unotools/textsearch.hxx> + +/* + * dialog returns the special field values "empty"/"not empty" + * as constants SC_EMPTYFIELDS and SC_NONEMPTYFIELDS respectively in nVal in + * conjunctions with the flag bQueryByString = FALSE. + */ + +#define SC_EMPTYFIELDS (double(0x0042)) +#define SC_NONEMPTYFIELDS (double(0x0043)) +#define SC_TEXTCOLOR (double(0x0044)) +#define SC_BACKGROUNDCOLOR (double(0x0045)) + +bool ScQueryEntry::Item::operator== (const Item& r) const +{ + return meType == r.meType && mfVal == r.mfVal && maString == r.maString && mbMatchEmpty == r.mbMatchEmpty + && mbRoundForFilter == r.mbRoundForFilter; +} + +ScQueryEntry::ScQueryEntry() : + bDoQuery(false), + nField(0), + eOp(SC_EQUAL), + eConnect(SC_AND), + maQueryItems(1) +{ +} + +ScQueryEntry::ScQueryEntry(const ScQueryEntry& r) : + bDoQuery(r.bDoQuery), + nField(r.nField), + eOp(r.eOp), + eConnect(r.eConnect), + maQueryItems(r.maQueryItems) +{ +} + +ScQueryEntry::~ScQueryEntry() +{ +} + +ScQueryEntry& ScQueryEntry::operator=( const ScQueryEntry& r ) +{ + bDoQuery = r.bDoQuery; + eOp = r.eOp; + eConnect = r.eConnect; + nField = r.nField; + maQueryItems = r.maQueryItems; + + pSearchParam.reset(); + pSearchText.reset(); + + return *this; +} + +void ScQueryEntry::SetQueryByEmpty() +{ + eOp = SC_EQUAL; + maQueryItems.resize(1); + Item& rItem = maQueryItems[0]; + rItem.meType = ByEmpty; + rItem.maString = svl::SharedString(); + rItem.mfVal = SC_EMPTYFIELDS; +} + +bool ScQueryEntry::IsQueryByEmpty() const +{ + if (maQueryItems.size() != 1) + return false; + + const Item& rItem = maQueryItems[0]; + return eOp == SC_EQUAL && + rItem.meType == ByEmpty && + rItem.maString.isEmpty() && + rItem.mfVal == SC_EMPTYFIELDS; +} + +void ScQueryEntry::SetQueryByNonEmpty() +{ + eOp = SC_EQUAL; + maQueryItems.resize(1); + Item& rItem = maQueryItems[0]; + rItem.meType = ByEmpty; + rItem.maString = svl::SharedString(); + rItem.mfVal = SC_NONEMPTYFIELDS; +} + +bool ScQueryEntry::IsQueryByNonEmpty() const +{ + if (maQueryItems.size() != 1) + return false; + + const Item& rItem = maQueryItems[0]; + return eOp == SC_EQUAL && + rItem.meType == ByEmpty && + rItem.maString.isEmpty() && + rItem.mfVal == SC_NONEMPTYFIELDS; +} + +void ScQueryEntry::SetQueryByTextColor(Color color) +{ + eOp = SC_EQUAL; + maQueryItems.resize(1); + Item& rItem = maQueryItems[0]; + rItem.meType = ByTextColor; + rItem.maString = svl::SharedString(); + rItem.mfVal = SC_TEXTCOLOR; + rItem.maColor = color; +} + +bool ScQueryEntry::IsQueryByTextColor() const +{ + if (maQueryItems.size() != 1) + return false; + + const Item& rItem = maQueryItems[0]; + return eOp == SC_EQUAL && + rItem.meType == ByTextColor; +} + +void ScQueryEntry::SetQueryByBackgroundColor(Color color) +{ + eOp = SC_EQUAL; + maQueryItems.resize(1); + Item& rItem = maQueryItems[0]; + rItem.meType = ByBackgroundColor; + rItem.maString = svl::SharedString(); + rItem.mfVal = SC_BACKGROUNDCOLOR; + rItem.maColor = color; +} + +bool ScQueryEntry::IsQueryByBackgroundColor() const +{ + if (maQueryItems.size() != 1) + return false; + + const Item& rItem = maQueryItems[0]; + return eOp == SC_EQUAL && + rItem.meType == ByBackgroundColor; +} + +ScQueryEntry::Item& ScQueryEntry::GetQueryItemImpl() const +{ + if (maQueryItems.size() != 1) + // Reset to a single query mode. + maQueryItems.resize(1); + return maQueryItems[0]; +} + +void ScQueryEntry::Clear() +{ + bDoQuery = false; + eOp = SC_EQUAL; + eConnect = SC_AND; + nField = 0; + maQueryItems.clear(); + maQueryItems.emplace_back(); + + pSearchParam.reset(); + pSearchText.reset(); +} + +bool ScQueryEntry::operator==( const ScQueryEntry& r ) const +{ + return bDoQuery == r.bDoQuery + && eOp == r.eOp + && eConnect == r.eConnect + && nField == r.nField + && maQueryItems == r.maQueryItems; + // do not compare pSearchParam and pSearchText! +} + +utl::TextSearch* ScQueryEntry::GetSearchTextPtr( utl::SearchParam::SearchType eSearchType, bool bCaseSens, + bool bWildMatchSel ) const +{ + if ( !pSearchParam ) + { + OUString aStr = maQueryItems[0].maString.getString(); + pSearchParam.reset(new utl::SearchParam( + aStr, eSearchType, bCaseSens, '~', bWildMatchSel)); + pSearchText.reset(new utl::TextSearch( *pSearchParam, ScGlobal::getCharClass() )); + } + return pSearchText.get(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/queryparam.cxx b/sc/source/core/tool/queryparam.cxx new file mode 100644 index 0000000000..286a1ef4ff --- /dev/null +++ b/sc/source/core/tool/queryparam.cxx @@ -0,0 +1,464 @@ +/* -*- 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 <memory> +#include <queryparam.hxx> +#include <queryentry.hxx> +#include <scmatrix.hxx> + +#include <svl/sharedstringpool.hxx> +#include <svl/numformat.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> + +#include <algorithm> + +namespace { + +const size_t MAXQUERY = 8; + +class FindByField +{ + SCCOLROW mnField; +public: + explicit FindByField(SCCOLROW nField) : mnField(nField) {} + bool operator() (const ScQueryEntry& rpEntry) const + { + return rpEntry.bDoQuery && rpEntry.nField == mnField; + } +}; + +struct FindUnused +{ + bool operator() (const ScQueryEntry& rpEntry) const + { + return !rpEntry.bDoQuery; + } +}; + +} + +ScQueryParamBase::const_iterator ScQueryParamBase::begin() const +{ + return m_Entries.begin(); +} + +ScQueryParamBase::const_iterator ScQueryParamBase::end() const +{ + return m_Entries.end(); +} + +ScQueryParamBase::ScQueryParamBase() : + eSearchType(utl::SearchParam::SearchType::Normal), + bHasHeader(true), + bByRow(true), + bInplace(true), + bCaseSens(false), + bDuplicate(false), + mbRangeLookup(false) +{ + m_Entries.resize(MAXQUERY); +} + +ScQueryParamBase::ScQueryParamBase(const ScQueryParamBase& r) : + eSearchType(r.eSearchType), bHasHeader(r.bHasHeader), bByRow(r.bByRow), bInplace(r.bInplace), + bCaseSens(r.bCaseSens), bDuplicate(r.bDuplicate), mbRangeLookup(r.mbRangeLookup), + m_Entries(r.m_Entries) +{ +} + +ScQueryParamBase& ScQueryParamBase::operator=(const ScQueryParamBase& r) +{ + if (this != &r) + { + eSearchType = r.eSearchType; + bHasHeader = r.bHasHeader; + bByRow = r.bByRow; + bInplace = r.bInplace; + bCaseSens = r.bCaseSens; + bDuplicate = r.bDuplicate; + mbRangeLookup = r.mbRangeLookup; + m_Entries = r.m_Entries; + } + return *this; +} + +ScQueryParamBase::~ScQueryParamBase() +{ +} + +bool ScQueryParamBase::IsValidFieldIndex() const +{ + return true; +} + +SCSIZE ScQueryParamBase::GetEntryCount() const +{ + return m_Entries.size(); +} + +const ScQueryEntry& ScQueryParamBase::GetEntry(SCSIZE n) const +{ + return m_Entries[n]; +} + +ScQueryEntry& ScQueryParamBase::GetEntry(SCSIZE n) +{ + return m_Entries[n]; +} + +ScQueryEntry& ScQueryParamBase::AppendEntry() +{ + // Find the first unused entry. + EntriesType::iterator itr = std::find_if( + m_Entries.begin(), m_Entries.end(), FindUnused()); + + if (itr != m_Entries.end()) + // Found! + return *itr; + + // Add a new entry to the end. + m_Entries.push_back(ScQueryEntry()); + return m_Entries.back(); +} + +ScQueryEntry* ScQueryParamBase::FindEntryByField(SCCOLROW nField, bool bNew) +{ + EntriesType::iterator itr = std::find_if( + m_Entries.begin(), m_Entries.end(), FindByField(nField)); + + if (itr != m_Entries.end()) + { + // existing entry found! + return &*itr; + } + + if (!bNew) + // no existing entry found, and we are not creating a new one. + return nullptr; + + return &AppendEntry(); +} + +std::vector<ScQueryEntry*> ScQueryParamBase::FindAllEntriesByField(SCCOLROW nField) +{ + std::vector<ScQueryEntry*> aEntries; + + auto fFind = FindByField(nField); + + for (auto& rxEntry : m_Entries) + if (fFind(rxEntry)) + aEntries.push_back(&rxEntry); + + return aEntries; +} + +bool ScQueryParamBase::RemoveEntryByField(SCCOLROW nField) +{ + EntriesType::iterator itr = std::find_if( + m_Entries.begin(), m_Entries.end(), FindByField(nField)); + bool bRet = false; + + if (itr != m_Entries.end()) + { + m_Entries.erase(itr); + if (m_Entries.size() < MAXQUERY) + // Make sure that we have at least MAXQUERY number of entries at + // all times. + m_Entries.resize(MAXQUERY); + bRet = true; + } + + return bRet; +} + +void ScQueryParamBase::RemoveAllEntriesByField(SCCOLROW nField) +{ + while( RemoveEntryByField( nField ) ) {} +} + +void ScQueryParamBase::Resize(size_t nNew) +{ + if (nNew < MAXQUERY) + nNew = MAXQUERY; // never less than MAXQUERY + + m_Entries.resize(nNew); +} + +void ScQueryParamBase::FillInExcelSyntax( + svl::SharedStringPool& rPool, const OUString& rCellStr, SCSIZE nIndex, SvNumberFormatter* pFormatter ) +{ + if (nIndex >= m_Entries.size()) + Resize(nIndex+1); + + ScQueryEntry& rEntry = GetEntry(nIndex); + ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + bool bByEmpty = false; + bool bByNonEmpty = false; + + if (rCellStr.isEmpty()) + rItem.maString = svl::SharedString::getEmptyString(); + else + { + rEntry.bDoQuery = true; + // Operatoren herausfiltern + if (rCellStr[0] == '<') + { + if (rCellStr.getLength() > 1 && rCellStr[1] == '>') + { + rItem.maString = rPool.intern(rCellStr.copy(2)); + rEntry.eOp = SC_NOT_EQUAL; + if (rCellStr.getLength() == 2) + bByNonEmpty = true; + } + else if (rCellStr.getLength() > 1 && rCellStr[1] == '=') + { + rItem.maString = rPool.intern(rCellStr.copy(2)); + rEntry.eOp = SC_LESS_EQUAL; + } + else + { + rItem.maString = rPool.intern(rCellStr.copy(1)); + rEntry.eOp = SC_LESS; + } + } + else if (rCellStr[0]== '>') + { + if (rCellStr.getLength() > 1 && rCellStr[1] == '=') + { + rItem.maString = rPool.intern(rCellStr.copy(2)); + rEntry.eOp = SC_GREATER_EQUAL; + } + else + { + rItem.maString = rPool.intern(rCellStr.copy(1)); + rEntry.eOp = SC_GREATER; + } + } + else + { + if (rCellStr[0] == '=') + { + rItem.maString = rPool.intern(rCellStr.copy(1)); + if (rCellStr.getLength() == 1) + bByEmpty = true; + } + else + rItem.maString = rPool.intern(rCellStr); + rEntry.eOp = SC_EQUAL; + } + } + + if (!pFormatter) + return; + + /* TODO: pFormatter currently is also used as a flag whether matching + * empty cells with an empty string is triggered from the interpreter. + * This could be handled independently if all queries should support + * it, needs to be evaluated if that actually is desired. */ + + // Interpreter queries have only one query, also QueryByEmpty and + // QueryByNonEmpty rely on that. + if (nIndex != 0) + return; + + // (empty = empty) is a match, and (empty <> not-empty) also is a + // match. (empty = 0) is not a match. + rItem.mbMatchEmpty = ((rEntry.eOp == SC_EQUAL && rItem.maString.isEmpty()) + || (rEntry.eOp == SC_NOT_EQUAL && !rItem.maString.isEmpty())); + + // SetQueryBy override item members with special values, so do this last. + if (bByEmpty) + rEntry.SetQueryByEmpty(); + else if (bByNonEmpty) + rEntry.SetQueryByNonEmpty(); + else + { + sal_uInt32 nFormat = 0; + bool bNumber = pFormatter->IsNumberFormat( rItem.maString.getString(), nFormat, rItem.mfVal); + rItem.meType = bNumber ? ScQueryEntry::ByValue : ScQueryEntry::ByString; + } +} + +ScQueryParamTable::ScQueryParamTable() : + nCol1(0),nRow1(0),nCol2(0),nRow2(0),nTab(0) +{ +} + +ScQueryParamTable::~ScQueryParamTable() +{ +} + +ScQueryParam::ScQueryParam() : + bDestPers(true), + nDestTab(0), + nDestCol(0), + nDestRow(0) +{ + Clear(); +} + +ScQueryParam::ScQueryParam( const ScQueryParam& ) = default; + +ScQueryParam::ScQueryParam( const ScDBQueryParamInternal& r ) : + ScQueryParamBase(r), + ScQueryParamTable(r), + bDestPers(true), + nDestTab(0), + nDestCol(0), + nDestRow(0) +{ +} + +ScQueryParam::~ScQueryParam() +{ +} + +void ScQueryParam::Clear() +{ + nCol1=nCol2 = 0; + nRow1=nRow2 = 0; + nTab = SCTAB_MAX; + eSearchType = utl::SearchParam::SearchType::Normal; + bHasHeader = bCaseSens = false; + bInplace = bByRow = bDuplicate = true; + + for (auto & itr : m_Entries) + { + itr.Clear(); + } + + ClearDestParams(); +} + +void ScQueryParam::ClearDestParams() +{ + bDestPers = true; + nDestTab = 0; + nDestCol = 0; + nDestRow = 0; +} + +ScQueryParam& ScQueryParam::operator=( const ScQueryParam& ) = default; + +bool ScQueryParam::operator==( const ScQueryParam& rOther ) const +{ + bool bEqual = false; + + // Are the number of queries equal? + SCSIZE nUsed = 0; + SCSIZE nOtherUsed = 0; + SCSIZE nEntryCount = GetEntryCount(); + SCSIZE nOtherEntryCount = rOther.GetEntryCount(); + + while (nUsed<nEntryCount && m_Entries[nUsed].bDoQuery) ++nUsed; + while (nOtherUsed<nOtherEntryCount && rOther.m_Entries[nOtherUsed].bDoQuery) + ++nOtherUsed; + + if ( (nUsed == nOtherUsed) + && (nCol1 == rOther.nCol1) + && (nRow1 == rOther.nRow1) + && (nCol2 == rOther.nCol2) + && (nRow2 == rOther.nRow2) + && (nTab == rOther.nTab) + && (bHasHeader == rOther.bHasHeader) + && (bByRow == rOther.bByRow) + && (bInplace == rOther.bInplace) + && (bCaseSens == rOther.bCaseSens) + && (eSearchType == rOther.eSearchType) + && (bDuplicate == rOther.bDuplicate) + && (bDestPers == rOther.bDestPers) + && (nDestTab == rOther.nDestTab) + && (nDestCol == rOther.nDestCol) + && (nDestRow == rOther.nDestRow) ) + { + bEqual = true; + for ( SCSIZE i=0; i<nUsed && bEqual; i++ ) + bEqual = m_Entries[i] == rOther.m_Entries[i]; + } + return bEqual; +} + +void ScQueryParam::MoveToDest() +{ + if (!bInplace) + { + SCCOL nDifX = nDestCol - nCol1; + SCROW nDifY = nDestRow - nRow1; + SCTAB nDifZ = nDestTab - nTab; + + nCol1 = sal::static_int_cast<SCCOL>( nCol1 + nDifX ); + nRow1 = sal::static_int_cast<SCROW>( nRow1 + nDifY ); + nCol2 = sal::static_int_cast<SCCOL>( nCol2 + nDifX ); + nRow2 = sal::static_int_cast<SCROW>( nRow2 + nDifY ); + nTab = sal::static_int_cast<SCTAB>( nTab + nDifZ ); + size_t n = m_Entries.size(); + for (size_t i=0; i<n; i++) + m_Entries[i].nField += nDifX; + + bInplace = true; + } + else + { + OSL_FAIL("MoveToDest, bInplace == TRUE"); + } +} + +ScDBQueryParamBase::ScDBQueryParamBase(DataType eType) : + mnField(-1), + mbSkipString(true), + meType(eType) +{ +} + +ScDBQueryParamBase::~ScDBQueryParamBase() +{ +} + +ScDBQueryParamInternal::ScDBQueryParamInternal() : + ScDBQueryParamBase(ScDBQueryParamBase::INTERNAL) +{ +} + +ScDBQueryParamInternal::~ScDBQueryParamInternal() +{ +} + +bool ScDBQueryParamInternal::IsValidFieldIndex() const +{ + return nCol1 <= mnField && mnField <= nCol2; +} + +ScDBQueryParamMatrix::ScDBQueryParamMatrix() : + ScDBQueryParamBase(ScDBQueryParamBase::MATRIX) +{ +} + +bool ScDBQueryParamMatrix::IsValidFieldIndex() const +{ + SCSIZE nC, nR; + mpMatrix->GetDimensions(nC, nR); + return 0 <= mnField && o3tl::make_unsigned(mnField) <= nC; +} + +ScDBQueryParamMatrix::~ScDBQueryParamMatrix() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/rangecache.cxx b/sc/source/core/tool/rangecache.cxx new file mode 100644 index 0000000000..f42d368436 --- /dev/null +++ b/sc/source/core/tool/rangecache.cxx @@ -0,0 +1,198 @@ +/* -*- 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 <rangecache.hxx> +#include <cellvalue.hxx> +#include <document.hxx> +#include <brdcst.hxx> +#include <queryevaluator.hxx> +#include <queryparam.hxx> + +#include <sal/log.hxx> +#include <svl/numformat.hxx> +#include <unotools/collatorwrapper.hxx> + +static bool needsDescending(ScQueryOp op) +{ + assert(op == SC_GREATER || op == SC_GREATER_EQUAL || op == SC_LESS || op == SC_LESS_EQUAL + || op == SC_EQUAL); + // We want all matching values to start in the sort order, + // since the data is searched from start until the last matching one. + return op == SC_GREATER || op == SC_GREATER_EQUAL; +} + +static ScSortedRangeCache::ValueType toValueType(const ScQueryParam& param) +{ + assert(param.GetEntry(0).bDoQuery && !param.GetEntry(1).bDoQuery + && param.GetEntry(0).GetQueryItems().size() == 1); + assert(param.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByString + || param.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByValue); + if (param.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByValue) + return ScSortedRangeCache::ValueType::Values; + return param.bCaseSens ? ScSortedRangeCache::ValueType::StringsCaseSensitive + : ScSortedRangeCache::ValueType::StringsCaseInsensitive; +} + +ScSortedRangeCache::ScSortedRangeCache(ScDocument* pDoc, const ScRange& rRange, + const ScQueryParam& param, ScInterpreterContext* context, + bool invalid) + : maRange(rRange) + , mpDoc(pDoc) + , mValid(false) + , mValueType(toValueType(param)) +{ + assert(maRange.aStart.Col() == maRange.aEnd.Col()); + assert(maRange.aStart.Tab() == maRange.aEnd.Tab()); + SCTAB nTab = maRange.aStart.Tab(); + SCTAB nCol = maRange.aStart.Col(); + assert(param.GetEntry(0).bDoQuery && !param.GetEntry(1).bDoQuery + && param.GetEntry(0).GetQueryItems().size() == 1); + const ScQueryEntry& entry = param.GetEntry(0); + const ScQueryEntry::Item& item = entry.GetQueryItem(); + mQueryOp = entry.eOp; + mQueryType = item.meType; + + if (invalid) + return; // leave empty + + SCROW startRow = maRange.aStart.Row(); + SCROW endRow = maRange.aEnd.Row(); + SCCOL startCol = maRange.aStart.Col(); + SCCOL endCol = maRange.aEnd.Col(); + if (!item.mbMatchEmpty) + if (!pDoc->ShrinkToDataArea(nTab, startCol, startRow, endCol, endRow)) + return; // no data cells, no need for a cache + + if (mValueType == ValueType::Values) + { + struct RowData + { + SCROW row; + double value; + }; + std::vector<RowData> rowData; + for (SCROW nRow = startRow; nRow <= endRow; ++nRow) + { + ScRefCellValue cell(pDoc->GetRefCellValue(ScAddress(nCol, nRow, nTab))); + if (ScQueryEvaluator::isQueryByValue(mQueryOp, mQueryType, cell)) + rowData.push_back(RowData{ nRow, cell.getValue() }); + else if (ScQueryEvaluator::isQueryByString(mQueryOp, mQueryType, cell)) + { + // Make sure that other possibilities in the generic handling + // in ScQueryEvaluator::processEntry() do not alter the results. + // (ByTextColor/ByBackgroundColor are blocked by CanBeUsedForSorterCache(), + // but isQueryByString() is possible if the cell content is a string. + // And including strings here would be tricky, as the string comparison + // may possibly(?) be different than a numeric one. So check if the string + // may possibly match a number, by converting it to one. If it can't match, + // then it's fine to ignore it (and it can happen e.g. if the query uses + // the whole column which includes a textual header). But if it can possibly + // match, then bail out and leave it to the unoptimized case. + // TODO Maybe it would actually work to use the numeric value obtained here? + if (!ScQueryEvaluator::isMatchWholeCell(*pDoc, mQueryOp)) + return; // substring matching cannot be sorted + sal_uInt32 format = 0; + double value; + if (context->GetFormatTable()->IsNumberFormat(cell.getString(pDoc), format, value)) + return; + } + } + std::stable_sort(rowData.begin(), rowData.end(), + [](const RowData& d1, const RowData& d2) { return d1.value < d2.value; }); + if (needsDescending(entry.eOp)) + for (auto it = rowData.rbegin(); it != rowData.rend(); ++it) + mSortedRows.emplace_back(it->row); + else + for (const RowData& d : rowData) + mSortedRows.emplace_back(d.row); + } + else + { + struct RowData + { + SCROW row; + OUString string; + }; + std::vector<RowData> rowData; + // Try to reuse as much ScQueryEvaluator code as possible, this should + // basically do the same comparisons. + assert(pDoc->FetchTable(nTab) != nullptr); + ScQueryEvaluator evaluator(*pDoc, *pDoc->FetchTable(nTab), param, context); + for (SCROW nRow = startRow; nRow <= endRow; ++nRow) + { + ScRefCellValue cell(pDoc->GetRefCellValue(ScAddress(nCol, nRow, nTab))); + // This should be used only with ScQueryEntry::ByString, and that + // means that ScQueryEvaluator::isQueryByString() should be the only + // possibility in the generic handling in ScQueryEvaluator::processEntry() + // (ByTextColor/ByBackgroundColor are blocked by CanBeUsedForSorterCache(), + // and isQueryByValue() is blocked by ScQueryEntry::ByString). + assert(mQueryType == ScQueryEntry::ByString); + assert(!ScQueryEvaluator::isQueryByValue(mQueryOp, mQueryType, cell)); + if (ScQueryEvaluator::isQueryByString(mQueryOp, mQueryType, cell)) + { + const svl::SharedString* sharedString = nullptr; + OUString string = evaluator.getCellString(cell, nRow, nCol, &sharedString); + if (sharedString) + string = sharedString->getString(); + rowData.push_back(RowData{ nRow, string }); + } + } + CollatorWrapper& collator + = ScGlobal::GetCollator(mValueType == ValueType::StringsCaseSensitive); + std::stable_sort(rowData.begin(), rowData.end(), + [&collator](const RowData& d1, const RowData& d2) { + return collator.compareString(d1.string, d2.string) < 0; + }); + if (needsDescending(entry.eOp)) + for (auto it = rowData.rbegin(); it != rowData.rend(); ++it) + mSortedRows.emplace_back(it->row); + else + for (const RowData& d : rowData) + mSortedRows.emplace_back(d.row); + } + + mRowToIndex.resize(maRange.aEnd.Row() - maRange.aStart.Row() + 1, mSortedRows.max_size()); + for (size_t i = 0; i < mSortedRows.size(); ++i) + mRowToIndex[mSortedRows[i] - maRange.aStart.Row()] = i; + mValid = true; +} + +void ScSortedRangeCache::Notify(const SfxHint& rHint) +{ + if (!mpDoc->IsInDtorClear()) + { + if (rHint.GetId() == SfxHintId::ScDataChanged || rHint.GetId() == SfxHintId::ScAreaChanged) + { + mpDoc->RemoveSortedRangeCache(*this); + // this ScSortedRangeCache is deleted by RemoveSortedRangeCache + } + } +} + +ScSortedRangeCache::HashKey ScSortedRangeCache::makeHashKey(const ScRange& range, + const ScQueryParam& param) +{ + assert(param.GetEntry(0).bDoQuery && !param.GetEntry(1).bDoQuery + && param.GetEntry(0).GetQueryItems().size() == 1); + const ScQueryEntry& entry = param.GetEntry(0); + const ScQueryEntry::Item& item = entry.GetQueryItem(); + return { range, toValueType(param), entry.eOp, item.meType }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/rangelst.cxx b/sc/source/core/tool/rangelst.cxx new file mode 100644 index 0000000000..f84c92c7a7 --- /dev/null +++ b/sc/source/core/tool/rangelst.cxx @@ -0,0 +1,1531 @@ +/* -*- 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 <stdlib.h> +#include <unotools/collatorwrapper.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> + +#include <rangelst.hxx> +#include <document.hxx> +#include <refupdat.hxx> +#include <compiler.hxx> +#include <algorithm> +#include <memory> + +using ::std::vector; +using ::std::find_if; +using ::std::for_each; +using ::formula::FormulaGrammar; + +namespace { + +template<typename T> +class FindEnclosingRange +{ +public: + explicit FindEnclosingRange(const T& rTest) : mrTest(rTest) {} + bool operator() (const ScRange & rRange) const + { + return rRange.Contains(mrTest); + } +private: + const T& mrTest; +}; + +template<typename T> +class FindIntersectingRange +{ +public: + explicit FindIntersectingRange(const T& rTest) : mrTest(rTest) {} + bool operator() (const ScRange & rRange) const + { + return rRange.Intersects(mrTest); + } +private: + const T& mrTest; +}; + +class CountCells +{ +public: + CountCells() : mnCellCount(0) {} + + void operator() (const ScRange & r) + { + mnCellCount += + sal_uInt64(r.aEnd.Col() - r.aStart.Col() + 1) + * sal_uInt64(r.aEnd.Row() - r.aStart.Row() + 1) + * sal_uInt64(r.aEnd.Tab() - r.aStart.Tab() + 1); + } + + sal_uInt64 getCellCount() const { return mnCellCount; } + +private: + sal_uInt64 mnCellCount; +}; + + +} + +// ScRangeList +ScRangeList::~ScRangeList() +{ +} + +ScRefFlags ScRangeList::Parse( std::u16string_view rStr, const ScDocument& rDoc, + formula::FormulaGrammar::AddressConvention eConv, + SCTAB nDefaultTab, sal_Unicode cDelimiter ) +{ + if ( !rStr.empty() ) + { + if (!cDelimiter) + cDelimiter = ScCompiler::GetNativeSymbolChar(ocSep); + + ScRefFlags nResult = ~ScRefFlags::ZERO; // set all bits + ScRange aRange; + const SCTAB nTab = nDefaultTab; + + sal_Int32 nPos = 0; + do + { + const OUString aOne( o3tl::getToken(rStr, 0, cDelimiter, nPos ) ); + aRange.aStart.SetTab( nTab ); // default tab if not specified + ScRefFlags nRes = aRange.ParseAny( aOne, rDoc, eConv ); + ScRefFlags nEndRangeBits = ScRefFlags::COL2_VALID | ScRefFlags::ROW2_VALID | ScRefFlags::TAB2_VALID; + ScRefFlags nTmp1 = nRes & ScRefFlags::BITS; + ScRefFlags nTmp2 = nRes & nEndRangeBits; + // If we have a valid single range with + // any of the address bits we are interested in + // set - set the equiv end range bits + if ( (nRes & ScRefFlags::VALID ) && (nTmp1 != ScRefFlags::ZERO) && ( nTmp2 != nEndRangeBits ) ) + applyStartToEndFlags(nRes, nTmp1); + + if ( nRes & ScRefFlags::VALID ) + push_back( aRange ); + nResult &= nRes; // all common bits are preserved + } + while (nPos >= 0); + + return nResult; // ScRefFlags::VALID set when all are OK + } + else + return ScRefFlags::ZERO; +} + +void ScRangeList::Format( OUString& rStr, ScRefFlags nFlags, const ScDocument& rDoc, + formula::FormulaGrammar::AddressConvention eConv, + sal_Unicode cDelimiter, bool bFullAddressNotation ) const +{ + if (!cDelimiter) + cDelimiter = ScCompiler::GetNativeSymbolChar(ocSep); + + OUStringBuffer aBuf; + bool bFirst = true; + for( auto const & r : maRanges) + { + if (bFirst) + bFirst = false; + else + aBuf.append(OUStringChar(cDelimiter)); + aBuf.append(r.Format(rDoc, nFlags, eConv, bFullAddressNotation)); + } + rStr = aBuf.makeStringAndClear(); +} + +void ScRangeList::Join( const ScRange& rNewRange, bool bIsInList ) +{ + if ( maRanges.empty() ) + { + push_back( rNewRange ); + return ; + } + + // One common usage is to join ranges that actually are top to bottom + // appends but the caller doesn't exactly know about it, e.g. when invoked + // by ScMarkData::FillRangeListWithMarks(), check for this special case + // first and speed up things by not looping over all ranges for each range + // to be joined. We don't remember the exact encompassing range that would + // have to be updated on refupdates and insertions and deletions, instead + // remember just the maximum row used, even independently of the sheet. + // This satisfies most use cases. + + if (!bIsInList) + { + const SCROW nRow1 = rNewRange.aStart.Row(); + if (nRow1 > mnMaxRowUsed + 1) + { + push_back( rNewRange ); + return; + } + else if (nRow1 == mnMaxRowUsed + 1) + { + // Check if we can simply enlarge the last range. + ScRange & rLast = maRanges.back(); + if (rLast.aEnd.Row() + 1 == nRow1 && + rLast.aStart.Col() == rNewRange.aStart.Col() && rLast.aEnd.Col() == rNewRange.aEnd.Col() && + rLast.aStart.Tab() == rNewRange.aStart.Tab() && rLast.aEnd.Tab() == rNewRange.aEnd.Tab()) + { + const SCROW nRow2 = rNewRange.aEnd.Row(); + rLast.aEnd.SetRow( nRow2 ); + mnMaxRowUsed = nRow2; + return; + } + } + } + + bool bJoinedInput = false; + const ScRange* pOver = &rNewRange; + +Label_Range_Join: + + assert(pOver); + const SCCOL nCol1 = pOver->aStart.Col(); + const SCROW nRow1 = pOver->aStart.Row(); + const SCCOL nTab1 = pOver->aStart.Tab(); + const SCCOL nCol2 = pOver->aEnd.Col(); + const SCROW nRow2 = pOver->aEnd.Row(); + const SCCOL nTab2 = pOver->aEnd.Tab(); + + size_t nOverPos = std::numeric_limits<size_t>::max(); + for (size_t i = 0; i < maRanges.size(); ++i) + { + ScRange & rRange = maRanges[i]; + if ( &rRange == pOver ) + { + nOverPos = i; + continue; // the same one, continue with the next + } + bool bJoined = false; + if ( rRange.Contains( *pOver ) ) + { // range pOver included in or identical to range p + // XXX if we never used Append() before Join() we could remove + // pOver and end processing, but it is not guaranteed and there can + // be duplicates. + if ( bIsInList ) + bJoined = true; // do away with range pOver + else + { // that was all then + bJoinedInput = true; // don't append + break; // for + } + } + else if ( pOver->Contains( rRange ) ) + { // range rRange included in range pOver, make pOver the new range + rRange = *pOver; + bJoined = true; + } + if ( !bJoined && rRange.aStart.Tab() == nTab1 && rRange.aEnd.Tab() == nTab2 ) + { // 2D + if ( rRange.aStart.Col() == nCol1 && rRange.aEnd.Col() == nCol2 ) + { + if ( rRange.aStart.Row() <= nRow2+1 && + rRange.aStart.Row() >= nRow1 ) + { // top + rRange.aStart.SetRow( nRow1 ); + bJoined = true; + } + else if ( rRange.aEnd.Row() >= nRow1-1 && + rRange.aEnd.Row() <= nRow2 ) + { // bottom + rRange.aEnd.SetRow( nRow2 ); + bJoined = true; + } + } + else if ( rRange.aStart.Row() == nRow1 && rRange.aEnd.Row() == nRow2 ) + { + if ( rRange.aStart.Col() <= nCol2+1 && + rRange.aStart.Col() >= nCol1 ) + { // left + rRange.aStart.SetCol( nCol1 ); + bJoined = true; + } + else if ( rRange.aEnd.Col() >= nCol1-1 && + rRange.aEnd.Col() <= nCol2 ) + { // right + rRange.aEnd.SetCol( nCol2 ); + bJoined = true; + } + } + } + if ( bJoined ) + { + if ( bIsInList ) + { // delete range pOver within the list + if (nOverPos != std::numeric_limits<size_t>::max()) + { + Remove(nOverPos); + if (nOverPos < i) + --i; + } + else + { + for (size_t nOver = 0, nRanges = maRanges.size(); nOver < nRanges; ++nOver) + { + if (&maRanges[nOver] == pOver) + { + Remove(nOver); + break; + } + } + } + } + bJoinedInput = true; + pOver = &maRanges[i]; + bIsInList = true; + goto Label_Range_Join; + } + } + if ( !bIsInList && !bJoinedInput ) + push_back( rNewRange ); +} + +void ScRangeList::AddAndPartialCombine( const ScRange& rNewRange ) +{ + if ( maRanges.empty() ) + { + push_back( rNewRange ); + return ; + } + + // One common usage is to join ranges that actually are top to bottom + // appends but the caller doesn't exactly know about it, e.g. when invoked + // by ScMarkData::FillRangeListWithMarks(), check for this special case + // first and speed up things by not looping over all ranges for each range + // to be joined. We don't remember the exact encompassing range that would + // have to be updated on refupdates and insertions and deletions, instead + // remember just the maximum row used, even independently of the sheet. + // This satisfies most use cases. + + const SCROW nRow1 = rNewRange.aStart.Row(); + if (nRow1 > mnMaxRowUsed + 1) + { + push_back( rNewRange ); + return; + } + + // scan backwards 2 rows to see if we can merge with anything + auto it = maRanges.rbegin(); + while (it != maRanges.rend() && it->aStart.Row() >= (rNewRange.aStart.Row() - 2)) + { + // Check if we can simply enlarge this range. + ScRange & rLast = *it; + if (rLast.aEnd.Row() + 1 == nRow1 && + rLast.aStart.Col() == rNewRange.aStart.Col() && rLast.aEnd.Col() == rNewRange.aEnd.Col() && + rLast.aStart.Tab() == rNewRange.aStart.Tab() && rLast.aEnd.Tab() == rNewRange.aEnd.Tab()) + { + const SCROW nRow2 = rNewRange.aEnd.Row(); + rLast.aEnd.SetRow( nRow2 ); + mnMaxRowUsed = std::max(mnMaxRowUsed, nRow2); + return; + } + ++it; + } + + push_back( rNewRange ); +} + +bool ScRangeList::operator==( const ScRangeList& r ) const +{ + if ( this == &r ) + return true; + + return maRanges == r.maRanges; +} + +bool ScRangeList::operator!=( const ScRangeList& r ) const +{ + return !operator==( r ); +} + +bool ScRangeList::UpdateReference( + UpdateRefMode eUpdateRefMode, + const ScDocument* pDoc, + const ScRange& rWhere, + SCCOL nDx, + SCROW nDy, + SCTAB nDz +) +{ + if (maRanges.empty()) + // No ranges to update. Bail out. + return false; + + bool bChanged = false; + SCCOL nCol1; + SCROW nRow1; + SCTAB nTab1; + SCCOL nCol2; + SCROW nRow2; + SCTAB nTab2; + rWhere.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + + if(eUpdateRefMode == URM_INSDEL) + { + // right now this only works for nTab1 == nTab2 + if(nTab1 == nTab2) + { + if(nDx < 0) + { + bChanged = DeleteArea(nCol1+nDx, nRow1, nTab1, nCol1-1, nRow2, nTab2); + } + if(nDy < 0) + { + bChanged = DeleteArea(nCol1, nRow1+nDy, nTab1, nCol2, nRow1-1, nTab2); + } + SAL_WARN_IF(nDx < 0 && nDy < 0, "sc", "nDx and nDy are negative, check why"); + } + } + + if(maRanges.empty()) + return true; + + for (auto& rR : maRanges) + { + SCCOL theCol1; + SCROW theRow1; + SCTAB theTab1; + SCCOL theCol2; + SCROW theRow2; + SCTAB theTab2; + rR.GetVars( theCol1, theRow1, theTab1, theCol2, theRow2, theTab2 ); + if ( ScRefUpdate::Update( pDoc, eUpdateRefMode, + nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, + nDx, nDy, nDz, + theCol1, theRow1, theTab1, theCol2, theRow2, theTab2 ) + != UR_NOTHING ) + { + bChanged = true; + rR.aStart.Set( theCol1, theRow1, theTab1 ); + rR.aEnd.Set( theCol2, theRow2, theTab2 ); + if (mnMaxRowUsed < theRow2) + mnMaxRowUsed = theRow2; + } + } + + if(eUpdateRefMode == URM_INSDEL) + { + if( nDx < 0 || nDy < 0 ) + { + size_t n = maRanges.size(); + for(size_t i = n-1; i > 0;) + { + Join(maRanges[i], true); + // Join() may merge and remove even more than one item, protect against it. + if(i >= maRanges.size()) + i = maRanges.size()-1; + else + --i; + } + } + } + + return bChanged; +} + +void ScRangeList::InsertRow( SCTAB nTab, SCCOL nColStart, SCCOL nColEnd, SCROW nRowPos, SCSIZE nSize ) +{ + std::vector<ScRange> aNewRanges; + for(const auto & rRange : maRanges) + { + if(rRange.aStart.Tab() <= nTab && rRange.aEnd.Tab() >= nTab) + { + if(rRange.aEnd.Row() == nRowPos - 1 && (nColStart <= rRange.aEnd.Col() || nColEnd >= rRange.aStart.Col())) + { + SCCOL nNewRangeStartCol = std::max<SCCOL>(nColStart, rRange.aStart.Col()); + SCCOL nNewRangeEndCol = std::min<SCCOL>(nColEnd, rRange.aEnd.Col()); + SCROW nNewRangeStartRow = rRange.aEnd.Row() + 1; + SCROW nNewRangeEndRow = nRowPos + nSize - 1; + aNewRanges.emplace_back(nNewRangeStartCol, nNewRangeStartRow, nTab, nNewRangeEndCol, + nNewRangeEndRow, nTab); + if (mnMaxRowUsed < nNewRangeEndRow) + mnMaxRowUsed = nNewRangeEndRow; + } + } + } + + for(const auto & rRange : aNewRanges) + { + if(!rRange.IsValid()) + continue; + + Join(rRange); + } +} + +void ScRangeList::InsertCol( SCTAB nTab, SCROW nRowStart, SCROW nRowEnd, SCCOL nColPos, SCSIZE nSize ) +{ + std::vector<ScRange> aNewRanges; + for(const auto & rRange : maRanges) + { + if(rRange.aStart.Tab() <= nTab && rRange.aEnd.Tab() >= nTab) + { + if(rRange.aEnd.Col() == nColPos - 1 && (nRowStart <= rRange.aEnd.Row() || nRowEnd >= rRange.aStart.Row())) + { + SCROW nNewRangeStartRow = std::max<SCROW>(nRowStart, rRange.aStart.Row()); + SCROW nNewRangeEndRow = std::min<SCROW>(nRowEnd, rRange.aEnd.Row()); + SCCOL nNewRangeStartCol = rRange.aEnd.Col() + 1; + SCCOL nNewRangeEndCol = nColPos + nSize - 1; + aNewRanges.emplace_back(nNewRangeStartCol, nNewRangeStartRow, nTab, nNewRangeEndCol, + nNewRangeEndRow, nTab); + } + } + } + + for(const auto & rRange : aNewRanges) + { + if(!rRange.IsValid()) + continue; + + Join(rRange); + } +} + +void ScRangeList::InsertCol( SCTAB nTab, SCCOL nCol ) +{ + std::vector<ScRange> aNewRanges; + for(const auto & rRange : maRanges) + { + if(rRange.aStart.Tab() <= nTab && rRange.aEnd.Tab() >= nTab) + { + if(rRange.aEnd.Col() == nCol - 1) + { + SCCOL nNewRangeStartCol = rRange.aEnd.Col() + 1; + SCCOL nNewRangeEndCol = nCol; + aNewRanges.emplace_back(nNewRangeStartCol, rRange.aStart.Row(), nTab, nNewRangeEndCol, + rRange.aEnd.Row(), nTab); + } + } + } + + for(const auto & rRange : aNewRanges) + { + if(!rRange.IsValid()) + continue; + + Join(rRange); + } +} + +namespace { + +/** + * Check if the deleting range cuts the test range exactly into a single + * piece. + * + * X = column ; Y = row + * +------+ +------+ + * |xxxxxx| | | + * +------+ or +------+ + * | | |xxxxxx| + * +------+ +------+ + * + * X = row; Y = column + * +--+--+ +--+--+ + * |xx| | | |xx| + * |xx| | or | |xx| + * |xx| | | |xx| + * +--+--+ +--+--+ + * where xxx is the deleted region. + */ +template<typename X, typename Y> +bool checkForOneRange( + X nDeleteX1, X nDeleteX2, Y nDeleteY1, Y nDeleteY2, X nX1, X nX2, Y nY1, Y nY2) +{ + return nDeleteX1 <= nX1 && nX2 <= nDeleteX2 && (nDeleteY1 <= nY1 || nY2 <= nDeleteY2); +} + +bool handleOneRange( const ScRange& rDeleteRange, ScRange& r ) +{ + const ScAddress& rDelStart = rDeleteRange.aStart; + const ScAddress& rDelEnd = rDeleteRange.aEnd; + ScAddress aPStart = r.aStart; + ScAddress aPEnd = r.aEnd; + SCCOL nDeleteCol1 = rDelStart.Col(); + SCCOL nDeleteCol2 = rDelEnd.Col(); + SCROW nDeleteRow1 = rDelStart.Row(); + SCROW nDeleteRow2 = rDelEnd.Row(); + SCCOL nCol1 = aPStart.Col(); + SCCOL nCol2 = aPEnd.Col(); + SCROW nRow1 = aPStart.Row(); + SCROW nRow2 = aPEnd.Row(); + + if (checkForOneRange(nDeleteCol1, nDeleteCol2, nDeleteRow1, nDeleteRow2, nCol1, nCol2, nRow1, nRow2)) + { + // Deleting range fully overlaps the column range. Adjust the row span. + if (nDeleteRow1 <= nRow1) + { + // +------+ + // |xxxxxx| + // +------+ + // | | + // +------+ (xxx) = deleted region + + r.aStart.SetRow(nDeleteRow1+1); + return true; + } + else if (nRow2 <= nDeleteRow2) + { + // +------+ + // | | + // +------+ + // |xxxxxx| + // +------+ (xxx) = deleted region + + r.aEnd.SetRow(nDeleteRow1-1); + return true; + } + } + else if (checkForOneRange(nDeleteRow1, nDeleteRow2, nDeleteCol1, nDeleteCol2, nRow1, nRow2, nCol1, nCol2)) + { + // Deleting range fully overlaps the row range. Adjust the column span. + if (nDeleteCol1 <= nCol1) + { + // +--+--+ + // |xx| | + // |xx| | + // |xx| | + // +--+--+ (xxx) = deleted region + + r.aStart.SetCol(nDeleteCol2+1); + return true; + } + else if (nCol2 <= nDeleteCol2) + { + // +--+--+ + // | |xx| + // | |xx| + // | |xx| + // +--+--+ (xxx) = deleted region + + r.aEnd.SetCol(nDeleteCol1-1); + return true; + } + } + return false; +} + +bool handleTwoRanges( const ScRange& rDeleteRange, ScRange& r, std::vector<ScRange>& rNewRanges ) +{ + const ScAddress& rDelStart = rDeleteRange.aStart; + const ScAddress& rDelEnd = rDeleteRange.aEnd; + ScAddress aPStart = r.aStart; + ScAddress aPEnd = r.aEnd; + SCCOL nDeleteCol1 = rDelStart.Col(); + SCCOL nDeleteCol2 = rDelEnd.Col(); + SCROW nDeleteRow1 = rDelStart.Row(); + SCROW nDeleteRow2 = rDelEnd.Row(); + SCCOL nCol1 = aPStart.Col(); + SCCOL nCol2 = aPEnd.Col(); + SCROW nRow1 = aPStart.Row(); + SCROW nRow2 = aPEnd.Row(); + SCTAB nTab = aPStart.Tab(); + + if (nCol1 < nDeleteCol1 && nDeleteCol1 <= nCol2 && nCol2 <= nDeleteCol2) + { + // column deleted : |-------| + // column original: |-------| + if (nRow1 < nDeleteRow1 && nDeleteRow1 <= nRow2 && nRow2 <= nDeleteRow2) + { + // row deleted: |------| + // row original: |------| + // + // +-------+ + // | 1 | + // +---+---+---+ + // | 2 |xxxxxxx| + // +---+xxxxxxx| + // |xxxxxxx| + // +-------+ (xxx) deleted region + + ScRange aNewRange( nCol1, nDeleteRow1, nTab, nDeleteCol1-1, nRow2, nTab ); // 2 + rNewRanges.push_back(aNewRange); + + r.aEnd.SetRow(nDeleteRow1-1); // 1 + return true; + } + else if (nRow1 <= nDeleteRow2 && nDeleteRow2 < nRow2 && nDeleteRow1 <= nRow1) + { + // row deleted: |------| + // row original: |------| + // + // +-------+ + // |xxxxxxx| + // +---+xxxxxxx| + // | 1 |xxxxxxx| + // +---+---+---+ + // | 2 | (xxx) deleted region + // +-------+ + + ScRange aNewRange( aPStart, ScAddress(nDeleteCol1-1, nRow2, nTab) ); // 1 + rNewRanges.push_back(aNewRange); + + r.aStart.SetRow(nDeleteRow2+1); // 2 + return true; + } + } + else if (nCol1 <= nDeleteCol2 && nDeleteCol2 < nCol2 && nDeleteCol1 <= nCol1) + { + // column deleted : |-------| + // column original: |-------| + if (nRow1 < nDeleteRow1 && nDeleteRow1 <= nRow2 && nRow2 <= nDeleteRow2) + { + // row deleted: |------| + // row original: |------| + // + // +-------+ + // | 1 | + // +-------+---+ + // |xxxxxxx| 2 | + // |xxxxxxx+---+ + // |xxxxxxx| + // +-------+ + // (xxx) deleted region + + ScRange aNewRange( ScAddress( nDeleteCol2+1, nDeleteRow1, nTab ), aPEnd ); // 2 + rNewRanges.push_back(aNewRange); + + r.aEnd.SetRow(nDeleteRow1-1); // 1 + return true; + } + else if (nRow1 <= nDeleteRow2 && nDeleteRow2 < nRow2 && nDeleteRow1 <= nRow1) + { + // row deleted: |-------| + // row original: |--------| + // + // +-------+ + // |xxxxxxx| + // |xxxxxxx+---+ + // |xxxxxxx| 1 | + // +-------+---+ + // | 2 | + // +-------+ (xxx) deleted region + + ScRange aNewRange(nDeleteCol2+1, nRow1, nTab, nCol2, nDeleteRow2, nTab); // 1 + rNewRanges.push_back(aNewRange); + + r.aStart.SetRow(nDeleteRow2+1); // 2 + return true; + } + } + else if (nRow1 < nDeleteRow1 && nDeleteRow2 < nRow2 && nDeleteCol1 <= nCol1 && nCol2 <= nDeleteCol2) + { + // +--------+ + // | 1 | + // +--------+ + // |xxxxxxxx| (xxx) deleted region + // +--------+ + // | 2 | + // +--------+ + + ScRange aNewRange( aPStart, ScAddress(nCol2, nDeleteRow1-1, nTab) ); // 1 + rNewRanges.push_back(aNewRange); + + r.aStart.SetRow(nDeleteRow2+1); // 2 + return true; + } + else if (nCol1 < nDeleteCol1 && nDeleteCol2 < nCol2 && nDeleteRow1 <= nRow1 && nRow2 <= nDeleteRow2) + { + // +---+-+---+ + // | |x| | + // | |x| | + // | 1 |x| 2 | (xxx) deleted region + // | |x| | + // | |x| | + // +---+-+---+ + + ScRange aNewRange( aPStart, ScAddress(nDeleteCol1-1, nRow2, nTab) ); // 1 + rNewRanges.push_back(aNewRange); + + r.aStart.SetCol(nDeleteCol2+1); // 2 + return true; + } + + return false; +} + +/** + * Check if any of the following applies: + * + * X = column; Y = row + * +----------+ +----------+ + * | | | | + * | +-------+---+ +--+-------+ | + * | |xxxxxxxxxxx| or |xxxxxxxxxx| | + * | +-------+---+ +--+-------+ | + * | | | | + * +----------+ +----------+ + * + * X = row; Y = column + * +--+ + * |xx| + * +---+xx+---+ +----------+ + * | |xx| | | | + * | |xx| | or | +--+ | + * | +--+ | | |xx| | + * | | | |xx| | + * +----------+ +---+xx+---+ + * |xx| + * +--+ (xxx) deleted region + */ +template<typename X, typename Y> +bool checkForThreeRanges( + X nDeleteX1, X nDeleteX2, Y nDeleteY1, Y nDeleteY2, X nX1, X nX2, Y nY1, Y nY2) +{ + if (nX1 <= nDeleteX1 && nX2 <= nDeleteX2 && nY1 < nDeleteY1 && nDeleteY2 < nY2) + return true; + + if (nDeleteX1 <= nX1 && nDeleteX2 <= nX2 && nY1 < nDeleteY1 && nDeleteY2 < nY2) + return true; + + return false; +} + +bool handleThreeRanges( const ScRange& rDeleteRange, ScRange& r, std::vector<ScRange>& rNewRanges ) +{ + const ScAddress& rDelStart = rDeleteRange.aStart; + const ScAddress& rDelEnd = rDeleteRange.aEnd; + ScAddress aPStart = r.aStart; + ScAddress aPEnd = r.aEnd; + SCCOL nDeleteCol1 = rDelStart.Col(); + SCCOL nDeleteCol2 = rDelEnd.Col(); + SCROW nDeleteRow1 = rDelStart.Row(); + SCROW nDeleteRow2 = rDelEnd.Row(); + SCCOL nCol1 = aPStart.Col(); + SCCOL nCol2 = aPEnd.Col(); + SCROW nRow1 = aPStart.Row(); + SCROW nRow2 = aPEnd.Row(); + SCTAB nTab = aPStart.Tab(); + + if (checkForThreeRanges(nDeleteCol1, nDeleteCol2, nDeleteRow1, nDeleteRow2, nCol1, nCol2, nRow1, nRow2)) + { + if (nCol1 < nDeleteCol1) + { + // +---+------+ + // | | 2 | + // | +------+---+ + // | 1 |xxxxxxxxxx| + // | +------+---+ + // | | 3 | + // +---+------+ + + ScRange aNewRange(nDeleteCol1, nRow1, nTab, nCol2, nDeleteRow1-1, nTab); // 2 + rNewRanges.push_back(aNewRange); + + aNewRange = ScRange(ScAddress(nDeleteCol1, nDeleteRow2+1, nTab), aPEnd); // 3 + rNewRanges.push_back(aNewRange); + + r.aEnd.SetCol(nDeleteCol1-1); // 1 + } + else + { + // +------+---+ + // | 1 | | + // +---+------+ | + // |xxxxxxxxxx| 2 | + // +---+------+ | + // | 3 | | + // +------+---+ + + ScRange aNewRange(aPStart, ScAddress(nDeleteCol2, nDeleteRow1-1, nTab)); // 1 + rNewRanges.push_back(aNewRange); + + aNewRange = ScRange(nCol1, nDeleteRow2+1, nTab, nDeleteCol2, nRow2, nTab); // 3 + rNewRanges.push_back(aNewRange); + + r.aStart.SetCol(nDeleteCol2+1); // 2 + } + return true; + } + else if (checkForThreeRanges(nDeleteRow1, nDeleteRow2, nDeleteCol1, nDeleteCol2, nRow1, nRow2, nCol1, nCol2)) + { + if (nRow1 < nDeleteRow1) + { + // +----------+ + // | 1 | + // +---+--+---+ + // | |xx| | + // | 2 |xx| 3 | + // | |xx| | + // +---+xx+---+ + // |xx| + // +--+ + + ScRange aNewRange(nCol1, nDeleteRow1, nTab, nDeleteCol1-1, nRow2, nTab); // 2 + rNewRanges.push_back( aNewRange ); + + aNewRange = ScRange(ScAddress(nDeleteCol2+1, nDeleteRow1, nTab), aPEnd); // 3 + rNewRanges.push_back( aNewRange ); + + r.aEnd.SetRow(nDeleteRow1-1); // 1 + } + else + { + // +--+ + // |xx| + // +---+xx+---+ + // | 1 |xx| 2 | + // | |xx| | + // +---+--+---+ + // | 3 | + // +----------+ + + ScRange aNewRange(aPStart, ScAddress(nDeleteCol1-1, nDeleteRow2, nTab)); // 1 + rNewRanges.push_back(aNewRange); + + aNewRange = ScRange(nDeleteCol2+1, nRow1, nTab, nCol2, nDeleteRow2, nTab); // 2 + rNewRanges.push_back( aNewRange ); + + r.aStart.SetRow(nDeleteRow2+1); // 3 + } + return true; + } + + return false; +} + +bool handleFourRanges( const ScRange& rDelRange, ScRange& r, std::vector<ScRange>& rNewRanges ) +{ + const ScAddress& rDelStart = rDelRange.aStart; + const ScAddress& rDelEnd = rDelRange.aEnd; + ScAddress aPStart = r.aStart; + ScAddress aPEnd = r.aEnd; + SCCOL nDeleteCol1 = rDelStart.Col(); + SCCOL nDeleteCol2 = rDelEnd.Col(); + SCROW nDeleteRow1 = rDelStart.Row(); + SCROW nDeleteRow2 = rDelEnd.Row(); + SCCOL nCol1 = aPStart.Col(); + SCCOL nCol2 = aPEnd.Col(); + SCROW nRow1 = aPStart.Row(); + SCROW nRow2 = aPEnd.Row(); + SCTAB nTab = aPStart.Tab(); + + if (nCol1 < nDeleteCol1 && nDeleteCol2 < nCol2 && nRow1 < nDeleteRow1 && nDeleteRow2 < nRow2) + { + + // +---------------+ + // | 1 | + // +---+-------+---+ + // | |xxxxxxx| | + // | 2 |xxxxxxx| 3 | + // | |xxxxxxx| | + // +---+-------+---+ + // | 4 | + // +---------------+ + + ScRange aNewRange(ScAddress(nCol1, nDeleteRow2+1, nTab), aPEnd); // 4 + rNewRanges.push_back( aNewRange ); + + aNewRange = ScRange(nCol1, nDeleteRow1, nTab, nDeleteCol1-1, nDeleteRow2, nTab); // 2 + rNewRanges.push_back( aNewRange ); + + aNewRange = ScRange(nDeleteCol2+1, nDeleteRow1, nTab, nCol2, nDeleteRow2, nTab); // 3 + rNewRanges.push_back( aNewRange ); + + r.aEnd.SetRow(nDeleteRow1-1); // 1 + + return true; + } + + return false; +} + +} + +bool ScRangeList::DeleteArea( SCCOL nCol1, SCROW nRow1, SCTAB nTab1, + SCCOL nCol2, SCROW nRow2, SCTAB nTab2 ) +{ + bool bChanged = false; + ScRange aRange( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + for(size_t i = 0; i < maRanges.size();) + { + if(aRange.Contains(maRanges[i])) + { + Remove(i); + bChanged = true; + } + else + ++i; + } + + std::vector<ScRange> aNewRanges; + + for(auto & rRange : maRanges) + { + // we have two basic cases here: + // 1. Delete area and pRange intersect + // 2. Delete area and pRange are not intersecting + // checking for 2 and if true skip this range + if(!rRange.Intersects(aRange)) + continue; + + // We get between 1 and 4 ranges from the difference of the first with the second + + // X either Col or Row and Y then the opposite + // r = deleteRange, p = entry from ScRangeList + + // getting exactly one range is the simple case + // r.aStart.X() <= p.aStart.X() && r.aEnd.X() >= p.aEnd.X() + // && ( r.aStart.Y() <= p.aStart.Y() || r.aEnd.Y() >= r.aEnd.Y() ) + if(handleOneRange( aRange, rRange )) + { + bChanged = true; + continue; + } + + // getting two ranges + // r.aStart.X() + else if(handleTwoRanges( aRange, rRange, aNewRanges )) + { + bChanged = true; + continue; + } + + // getting 3 ranges + // r.aStart.X() > p.aStart.X() && r.aEnd.X() >= p.aEnd.X() + // && r.aStart.Y() > p.aStart.Y() && r.aEnd.Y() < p.aEnd.Y() + // or + // r.aStart.X() <= p.aStart.X() && r.aEnd.X() < p.aEnd.X() + // && r.aStart.Y() > p.aStart.Y() && r.aEnd.Y() < p.aEnd.Y() + else if(handleThreeRanges( aRange, rRange, aNewRanges )) + { + bChanged = true; + continue; + } + + // getting 4 ranges + // r.aStart.X() > p.aStart.X() && r.aEnd().X() < p.aEnd.X() + // && r.aStart.Y() > p.aStart.Y() && r.aEnd().Y() < p.aEnd.Y() + else if(handleFourRanges( aRange, rRange, aNewRanges )) + { + bChanged = true; + continue; + } + } + for(const auto & rRange : aNewRanges) + Join(rRange); + + return bChanged; +} + +const ScRange* ScRangeList::Find( const ScAddress& rAdr ) const +{ + auto itr = find_if( + maRanges.cbegin(), maRanges.cend(), FindEnclosingRange<ScAddress>(rAdr)); + return itr == maRanges.end() ? nullptr : &*itr; +} + +ScRange* ScRangeList::Find( const ScAddress& rAdr ) +{ + auto itr = find_if( + maRanges.begin(), maRanges.end(), FindEnclosingRange<ScAddress>(rAdr)); + return itr == maRanges.end() ? nullptr : &*itr; +} + +ScRangeList::ScRangeList() : mnMaxRowUsed(-1) {} + +ScRangeList::ScRangeList( const ScRangeList& rList ) : + SvRefBase(rList), + maRanges(rList.maRanges), + mnMaxRowUsed(rList.mnMaxRowUsed) +{ +} + +ScRangeList::ScRangeList(ScRangeList&& rList) noexcept : + maRanges(std::move(rList.maRanges)), + mnMaxRowUsed(rList.mnMaxRowUsed) +{ +} + +ScRangeList::ScRangeList( const ScRange& rRange ) : + mnMaxRowUsed(-1) +{ + maRanges.reserve(1); + push_back(rRange); +} + +ScRangeList& ScRangeList::operator=(const ScRangeList& rList) +{ + maRanges = rList.maRanges; + mnMaxRowUsed = rList.mnMaxRowUsed; + return *this; +} + +ScRangeList& ScRangeList::operator=(ScRangeList&& rList) noexcept +{ + maRanges = std::move(rList.maRanges); + mnMaxRowUsed = rList.mnMaxRowUsed; + return *this; +} + +bool ScRangeList::Intersects( const ScRange& rRange ) const +{ + return std::any_of(maRanges.begin(), maRanges.end(), FindIntersectingRange<ScRange>(rRange)); +} + +bool ScRangeList::Contains( const ScRange& rRange ) const +{ + return std::any_of(maRanges.begin(), maRanges.end(), FindEnclosingRange<ScRange>(rRange)); +} + +sal_uInt64 ScRangeList::GetCellCount() const +{ + CountCells func; + return for_each(maRanges.begin(), maRanges.end(), func).getCellCount(); +} + +void ScRangeList::Remove(size_t nPos) +{ + if (maRanges.size() <= nPos) + // Out-of-bound condition. Bail out. + return; + maRanges.erase(maRanges.begin() + nPos); +} + +void ScRangeList::RemoveAll() +{ + maRanges.clear(); + mnMaxRowUsed = -1; +} + +ScRange ScRangeList::Combine() const +{ + if (maRanges.empty()) + return ScRange(); + + auto itr = maRanges.cbegin(), itrEnd = maRanges.cend(); + ScRange aRet = *itr; + ++itr; + for (; itr != itrEnd; ++itr) + { + const ScRange& r = *itr; + SCROW nRow1 = r.aStart.Row(), nRow2 = r.aEnd.Row(); + SCCOL nCol1 = r.aStart.Col(), nCol2 = r.aEnd.Col(); + SCTAB nTab1 = r.aStart.Tab(), nTab2 = r.aEnd.Tab(); + if (aRet.aStart.Row() > nRow1) + aRet.aStart.SetRow(nRow1); + if (aRet.aStart.Col() > nCol1) + aRet.aStart.SetCol(nCol1); + if (aRet.aStart.Tab() > nTab1) + aRet.aStart.SetTab(nTab1); + if (aRet.aEnd.Row() < nRow2) + aRet.aEnd.SetRow(nRow2); + if (aRet.aEnd.Col() < nCol2) + aRet.aEnd.SetCol(nCol2); + if (aRet.aEnd.Tab() < nTab2) + aRet.aEnd.SetTab(nTab2); + } + return aRet; +} + +void ScRangeList::push_back(const ScRange & r) +{ + maRanges.push_back(r); + if (mnMaxRowUsed < r.aEnd.Row()) + mnMaxRowUsed = r.aEnd.Row(); +} + +void ScRangeList::swap( ScRangeList& r ) +{ + maRanges.swap(r.maRanges); + std::swap(mnMaxRowUsed, r.mnMaxRowUsed); +} + +ScAddress ScRangeList::GetTopLeftCorner() const +{ + if(empty()) + return ScAddress(); + + ScAddress const * pAddr = &maRanges[0].aStart; + for(size_t i = 1, n = size(); i < n; ++i) + { + if(maRanges[i].aStart < *pAddr) + pAddr = &maRanges[i].aStart; + } + + return *pAddr; +} + +ScRangeList ScRangeList::GetIntersectedRange(const ScRange& rRange) const +{ + ScRangeList aReturn; + for(auto& rR : maRanges) + { + if(rR.Intersects(rRange)) + { + SCCOL nColStart1, nColEnd1, nColStart2, nColEnd2; + SCROW nRowStart1, nRowEnd1, nRowStart2, nRowEnd2; + SCTAB nTabStart1, nTabEnd1, nTabStart2, nTabEnd2; + rR.GetVars(nColStart1, nRowStart1, nTabStart1, + nColEnd1, nRowEnd1, nTabEnd1); + rRange.GetVars(nColStart2, nRowStart2, nTabStart2, + nColEnd2, nRowEnd2, nTabEnd2); + + ScRange aNewRange(std::max<SCCOL>(nColStart1, nColStart2), std::max<SCROW>(nRowStart1, nRowStart2), + std::max<SCTAB>(nTabStart1, nTabStart2), std::min<SCCOL>(nColEnd1, nColEnd2), + std::min<SCROW>(nRowEnd1, nRowEnd2), std::min<SCTAB>(nTabEnd1, nTabEnd2)); + aReturn.Join(aNewRange); + } + } + + return aReturn; +} + +// ScRangePairList +ScRangePairList::~ScRangePairList() +{ +} + +void ScRangePairList::Remove(size_t nPos) +{ + if (maPairs.size() <= nPos) + // Out-of-bound condition. Bail out. + return; + maPairs.erase(maPairs.begin() + nPos); +} + +void ScRangePairList::Remove( const ScRangePair & rAdr) +{ + auto itr = std::find_if(maPairs.begin(), maPairs.end(), [&rAdr](const ScRangePair& rPair) { return &rAdr == &rPair; }); + if (itr != maPairs.end()) + { + maPairs.erase( itr ); + return; + } + assert(false); +} + +ScRangePair & ScRangePairList::operator [](size_t idx) +{ + return maPairs[idx]; +} + +const ScRangePair & ScRangePairList::operator [](size_t idx) const +{ + return maPairs[idx]; +} + +size_t ScRangePairList::size() const +{ + return maPairs.size(); +} + +void ScRangePairList::UpdateReference( UpdateRefMode eUpdateRefMode, + const ScDocument* pDoc, const ScRange& rWhere, + SCCOL nDx, SCROW nDy, SCTAB nDz ) +{ + if ( maPairs.empty() ) + return; + + SCCOL nCol1; + SCROW nRow1; + SCTAB nTab1; + SCCOL nCol2; + SCROW nRow2; + SCTAB nTab2; + rWhere.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + for (ScRangePair & rR : maPairs) + { + for ( sal_uInt16 j=0; j<2; j++ ) + { + ScRange& rRange = rR.GetRange(j); + SCCOL theCol1; + SCROW theRow1; + SCTAB theTab1; + SCCOL theCol2; + SCROW theRow2; + SCTAB theTab2; + rRange.GetVars( theCol1, theRow1, theTab1, theCol2, theRow2, theTab2 ); + if ( ScRefUpdate::Update( pDoc, eUpdateRefMode, + nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, + nDx, nDy, nDz, + theCol1, theRow1, theTab1, theCol2, theRow2, theTab2 ) + != UR_NOTHING ) + { + rRange.aStart.Set( theCol1, theRow1, theTab1 ); + rRange.aEnd.Set( theCol2, theRow2, theTab2 ); + } + } + } +} + +// Delete entries that have the labels (first range) on nTab +void ScRangePairList::DeleteOnTab( SCTAB nTab ) +{ + std::erase_if(maPairs, + [&nTab](const ScRangePair& rR) { + const ScRange & rRange = rR.GetRange(0); + return (rRange.aStart.Tab() == nTab) && (rRange.aEnd.Tab() == nTab); + }); +} + +ScRangePair* ScRangePairList::Find( const ScAddress& rAdr ) +{ + for (ScRangePair & rR : maPairs) + { + if ( rR.GetRange(0).Contains( rAdr ) ) + return &rR; + } + return nullptr; +} + +ScRangePair* ScRangePairList::Find( const ScRange& rRange ) +{ + for (ScRangePair & rR : maPairs) + { + if ( rR.GetRange(0) == rRange ) + return &rR; + } + return nullptr; +} + +ScRangePairList* ScRangePairList::Clone() const +{ + ScRangePairList* pNew = new ScRangePairList; + for (const ScRangePair & rR : maPairs) + { + pNew->Append( rR ); + } + return pNew; +} + +namespace { + +class ScRangePairList_sortNameCompare +{ +public: + ScRangePairList_sortNameCompare(ScDocument& rDoc) : mrDoc(rDoc) {} + + bool operator()( const ScRangePair *ps1, const ScRangePair* ps2 ) const + { + const ScAddress& rStartPos1 = ps1->GetRange(0).aStart; + const ScAddress& rStartPos2 = ps2->GetRange(0).aStart; + OUString aStr1, aStr2; + sal_Int32 nComp; + if ( rStartPos1.Tab() == rStartPos2.Tab() ) + nComp = 0; + else + { + mrDoc.GetName( rStartPos1.Tab(), aStr1 ); + mrDoc.GetName( rStartPos2.Tab(), aStr2 ); + nComp = ScGlobal::GetCollator().compareString( aStr1, aStr2 ); + } + if (nComp < 0) + { + return true; // -1; + } + else if (nComp > 0) + { + return false; // 1; + } + + // equal tabs + if ( rStartPos1.Col() < rStartPos2.Col() ) + return true; // -1; + if ( rStartPos1.Col() > rStartPos2.Col() ) + return false; // 1; + // equal cols + if ( rStartPos1.Row() < rStartPos2.Row() ) + return true; // -1; + if ( rStartPos1.Row() > rStartPos2.Row() ) + return false; // 1; + + // first corner equal, second corner + const ScAddress& rEndPos1 = ps1->GetRange(0).aEnd; + const ScAddress& rEndPos2 = ps2->GetRange(0).aEnd; + if ( rEndPos1.Tab() == rEndPos2.Tab() ) + nComp = 0; + else + { + mrDoc.GetName( rEndPos1.Tab(), aStr1 ); + mrDoc.GetName( rEndPos2.Tab(), aStr2 ); + nComp = ScGlobal::GetCollator().compareString( aStr1, aStr2 ); + } + if (nComp < 0) + { + return true; // -1; + } + else if (nComp > 0) + { + return false; // 1; + } + + // equal tabs + if ( rEndPos1.Col() < rEndPos2.Col() ) + return true; // -1; + if ( rEndPos1.Col() > rEndPos2.Col() ) + return false; // 1; + // equal cols + if ( rEndPos1.Row() < rEndPos2.Row() ) + return true; // -1; + if ( rEndPos1.Row() > rEndPos2.Row() ) + return false; // 1; + + return false; + } +private: + ScDocument& mrDoc; +}; + +} + +void ScRangePairList::Join( const ScRangePair& r, bool bIsInList ) +{ + if ( maPairs.empty() ) + { + Append( r ); + return ; + } + + bool bJoinedInput = false; + const ScRangePair* pOver = &r; + +Label_RangePair_Join: + + assert(pOver); + const ScRange& r1 = pOver->GetRange(0); + const ScRange& r2 = pOver->GetRange(1); + const SCCOL nCol1 = r1.aStart.Col(); + const SCROW nRow1 = r1.aStart.Row(); + const SCTAB nTab1 = r1.aStart.Tab(); + const SCCOL nCol2 = r1.aEnd.Col(); + const SCROW nRow2 = r1.aEnd.Row(); + const SCTAB nTab2 = r1.aEnd.Tab(); + + size_t nOverPos = std::numeric_limits<size_t>::max(); + for (size_t i = 0; i < maPairs.size(); ++i) + { + ScRangePair & rPair = maPairs[ i ]; + if ( &rPair == pOver ) + { + nOverPos = i; + continue; // the same one, continue with the next + } + bool bJoined = false; + ScRange& rp1 = rPair.GetRange(0); + ScRange& rp2 = rPair.GetRange(1); + if ( rp2 == r2 ) + { // only if Range2 is equal + if ( rp1.Contains( r1 ) ) + { // RangePair pOver included in or identical to RangePair p + if ( bIsInList ) + bJoined = true; // do away with RangePair pOver + else + { // that was all then + bJoinedInput = true; // don't append + break; // for + } + } + else if ( r1.Contains( rp1 ) ) + { // RangePair p included in RangePair pOver, make pOver the new RangePair + rPair = *pOver; + bJoined = true; + } + } + if ( !bJoined && rp1.aStart.Tab() == nTab1 && rp1.aEnd.Tab() == nTab2 + && rp2.aStart.Tab() == r2.aStart.Tab() + && rp2.aEnd.Tab() == r2.aEnd.Tab() ) + { // 2D, Range2 must be located side-by-side just like Range1 + if ( rp1.aStart.Col() == nCol1 && rp1.aEnd.Col() == nCol2 + && rp2.aStart.Col() == r2.aStart.Col() + && rp2.aEnd.Col() == r2.aEnd.Col() ) + { + if ( rp1.aStart.Row() == nRow2+1 + && rp2.aStart.Row() == r2.aEnd.Row()+1 ) + { // top + rp1.aStart.SetRow( nRow1 ); + rp2.aStart.SetRow( r2.aStart.Row() ); + bJoined = true; + } + else if ( rp1.aEnd.Row() == nRow1-1 + && rp2.aEnd.Row() == r2.aStart.Row()-1 ) + { // bottom + rp1.aEnd.SetRow( nRow2 ); + rp2.aEnd.SetRow( r2.aEnd.Row() ); + bJoined = true; + } + } + else if ( rp1.aStart.Row() == nRow1 && rp1.aEnd.Row() == nRow2 + && rp2.aStart.Row() == r2.aStart.Row() + && rp2.aEnd.Row() == r2.aEnd.Row() ) + { + if ( rp1.aStart.Col() == nCol2+1 + && rp2.aStart.Col() == r2.aEnd.Col()+1 ) + { // left + rp1.aStart.SetCol( nCol1 ); + rp2.aStart.SetCol( r2.aStart.Col() ); + bJoined = true; + } + else if ( rp1.aEnd.Col() == nCol1-1 + && rp2.aEnd.Col() == r2.aEnd.Col()-1 ) + { // right + rp1.aEnd.SetCol( nCol2 ); + rp2.aEnd.SetCol( r2.aEnd.Col() ); + bJoined = true; + } + } + } + if ( bJoined ) + { + if ( bIsInList ) + { // delete RangePair pOver within the list + if (nOverPos != std::numeric_limits<size_t>::max()) + { + Remove(nOverPos); + if (nOverPos < i) + --i; + } + else + { + for (size_t nOver = 0, nRangePairs = maPairs.size(); nOver < nRangePairs; ++nOver) + { + if (&maPairs[nOver] == pOver) + { + maPairs.erase(maPairs.begin() + nOver); + break; + } + } + assert(false); + } + } + bJoinedInput = true; + pOver = &maPairs[i]; + bIsInList = true; + goto Label_RangePair_Join; + } + } + if ( !bIsInList && !bJoinedInput ) + Append( r ); +} + +std::vector<const ScRangePair*> ScRangePairList::CreateNameSortedArray( ScDocument& rDoc ) const +{ + std::vector<const ScRangePair*> aSortedVec(maPairs.size()); + size_t i = 0; + for ( auto const & rPair : maPairs) + { + aSortedVec[i++] = &rPair; + } + + std::sort( aSortedVec.begin(), aSortedVec.end(), ScRangePairList_sortNameCompare(rDoc) ); + + return aSortedVec; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/rangenam.cxx b/sc/source/core/tool/rangenam.cxx new file mode 100644 index 0000000000..051b0a5e77 --- /dev/null +++ b/sc/source/core/tool/rangenam.cxx @@ -0,0 +1,900 @@ +/* -*- 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 <string.h> +#include <memory> +#include <unotools/collatorwrapper.hxx> +#include <unotools/charclass.hxx> +#include <com/sun/star/sheet/NamedRangeFlag.hpp> +#include <osl/diagnose.h> + +#include <token.hxx> +#include <tokenarray.hxx> +#include <rangenam.hxx> +#include <rangeutl.hxx> +#include <global.hxx> +#include <compiler.hxx> +#include <refupdat.hxx> +#include <document.hxx> +#include <refupdatecontext.hxx> +#include <tokenstringcontext.hxx> + +#include <formula/errorcodes.hxx> + +using namespace formula; +using ::std::pair; + +// ScRangeData + +ScRangeData::ScRangeData( ScDocument& rDok, + const OUString& rName, + const OUString& rSymbol, + const ScAddress& rAddress, + Type nType, + const FormulaGrammar::Grammar eGrammar ) : + aName ( rName ), + aUpperName ( ScGlobal::getCharClass().uppercase( rName ) ), + aPos ( rAddress ), + eType ( nType ), + rDoc ( rDok ), + eTempGrammar( eGrammar ), + nIndex ( 0 ), + bModified ( false ) +{ + if (!rSymbol.isEmpty()) + { + // Let the compiler set an error on unknown names for a subsequent + // CompileUnresolvedXML(). + const bool bImporting = rDoc.IsImportingXML(); + CompileRangeData( rSymbol, bImporting); + if (bImporting) + rDoc.CheckLinkFormulaNeedingCheck( *pCode); + } + else + { + // #i63513#/#i65690# don't leave pCode as NULL. + // Copy ctor default-constructs pCode if it was NULL, so it's initialized here, too, + // to ensure same behavior if unnecessary copying is left out. + + pCode.reset( new ScTokenArray(rDoc) ); + pCode->SetFromRangeName(true); + } +} + +ScRangeData::ScRangeData( ScDocument& rDok, + const OUString& rName, + const ScTokenArray& rArr, + const ScAddress& rAddress, + Type nType ) : + aName ( rName ), + aUpperName ( ScGlobal::getCharClass().uppercase( rName ) ), + pCode ( new ScTokenArray( rArr ) ), + aPos ( rAddress ), + eType ( nType ), + rDoc ( rDok ), + eTempGrammar( FormulaGrammar::GRAM_UNSPECIFIED ), + nIndex ( 0 ), + bModified ( false ) +{ + pCode->SetFromRangeName(true); + InitCode(); +} + +ScRangeData::ScRangeData( ScDocument& rDok, + const OUString& rName, + const ScAddress& rTarget ) : + aName ( rName ), + aUpperName ( ScGlobal::getCharClass().uppercase( rName ) ), + pCode ( new ScTokenArray(rDok) ), + aPos ( rTarget ), + eType ( Type::Name ), + rDoc ( rDok ), + eTempGrammar( FormulaGrammar::GRAM_UNSPECIFIED ), + nIndex ( 0 ), + bModified ( false ) +{ + ScSingleRefData aRefData; + aRefData.InitAddress( rTarget ); + aRefData.SetFlag3D( true ); + pCode->AddSingleReference( aRefData ); + pCode->SetFromRangeName(true); + ScCompiler aComp( rDoc, aPos, *pCode, rDoc.GetGrammar() ); + aComp.CompileTokenArray(); + if ( pCode->GetCodeError() == FormulaError::NONE ) + eType |= Type::AbsPos; +} + +ScRangeData::ScRangeData(const ScRangeData& rScRangeData, ScDocument* pDocument, const ScAddress* pPos) : + aName (rScRangeData.aName), + aUpperName (rScRangeData.aUpperName), + pCode (rScRangeData.pCode ? rScRangeData.pCode->Clone().release() : new ScTokenArray(*pDocument)), // make real copy (not copy-ctor) + aPos (pPos ? *pPos : rScRangeData.aPos), + eType (rScRangeData.eType), + rDoc (pDocument ? *pDocument : rScRangeData.rDoc), + eTempGrammar(rScRangeData.eTempGrammar), + nIndex (rScRangeData.nIndex), + bModified (rScRangeData.bModified) +{ + pCode->SetFromRangeName(true); +} + +ScRangeData::~ScRangeData() +{ +} + +void ScRangeData::CompileRangeData( const OUString& rSymbol, bool bSetError ) +{ + if (eTempGrammar == FormulaGrammar::GRAM_UNSPECIFIED) + { + OSL_FAIL( "ScRangeData::CompileRangeData: unspecified grammar"); + // Anything is almost as bad as this, but we might have the best choice + // if not loading documents. + eTempGrammar = FormulaGrammar::GRAM_NATIVE; + } + + ScCompiler aComp( rDoc, aPos, eTempGrammar ); + if (bSetError) + aComp.SetExtendedErrorDetection( ScCompiler::EXTENDED_ERROR_DETECTION_NAME_NO_BREAK); + pCode = aComp.CompileString( rSymbol ); + pCode->SetFromRangeName(true); + if( pCode->GetCodeError() != FormulaError::NONE ) + return; + + FormulaTokenArrayPlainIterator aIter(*pCode); + FormulaToken* p = aIter.GetNextReference(); + if( p ) + { + // first token is a reference + /* FIXME: wouldn't that need a check if it's exactly one reference? */ + if( p->GetType() == svSingleRef ) + eType = eType | Type::AbsPos; + else + eType = eType | Type::AbsArea; + } + // For manual input set an error for an incomplete formula. + if (!rDoc.IsImportingXML()) + { + aComp.CompileTokenArray(); + pCode->DelRPN(); + } +} + +void ScRangeData::CompileUnresolvedXML( sc::CompileFormulaContext& rCxt ) +{ + if (pCode->GetCodeError() == FormulaError::NoName) + { + // Reconstruct the symbol/formula and then recompile. + OUString aSymbol; + rCxt.setGrammar(eTempGrammar); + ScCompiler aComp(rCxt, aPos, *pCode); + aComp.CreateStringFromTokenArray( aSymbol); + // Don't let the compiler set an error for unknown names on final + // compile, errors are handled by the interpreter thereafter. + CompileRangeData( aSymbol, false); + rCxt.getDoc().CheckLinkFormulaNeedingCheck( *pCode); + } +} + +#if DEBUG_FORMULA_COMPILER +void ScRangeData::Dump() const +{ + cout << "-- ScRangeData" << endl; + cout << " name: " << aName << endl; + cout << " ref position: (col=" << aPos.Col() << ", row=" << aPos.Row() << ", sheet=" << aPos.Tab() << ")" << endl; + + if (pCode) + pCode->Dump(); +} +#endif + +void ScRangeData::GuessPosition() +{ + // set a position that allows "absoluting" of all relative references + // in CalcAbsIfRel without errors + + OSL_ENSURE(aPos == ScAddress(), "position will go lost now"); + + SCCOL nMinCol = 0; + SCROW nMinRow = 0; + SCTAB nMinTab = 0; + + formula::FormulaToken* t; + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + while ( ( t = aIter.GetNextReference() ) != nullptr ) + { + ScSingleRefData& rRef1 = *t->GetSingleRef(); + if ( rRef1.IsColRel() && rRef1.Col() < nMinCol ) + nMinCol = rRef1.Col(); + if ( rRef1.IsRowRel() && rRef1.Row() < nMinRow ) + nMinRow = rRef1.Row(); + if ( rRef1.IsTabRel() && rRef1.Tab() < nMinTab ) + nMinTab = rRef1.Tab(); + + if ( t->GetType() == svDoubleRef ) + { + ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2; + if ( rRef2.IsColRel() && rRef2.Col() < nMinCol ) + nMinCol = rRef2.Col(); + if ( rRef2.IsRowRel() && rRef2.Row() < nMinRow ) + nMinRow = rRef2.Row(); + if ( rRef2.IsTabRel() && rRef2.Tab() < nMinTab ) + nMinTab = rRef2.Tab(); + } + } + + aPos = ScAddress( static_cast<SCCOL>(-nMinCol), static_cast<SCROW>(-nMinRow), static_cast<SCTAB>(-nMinTab) ); +} + +OUString ScRangeData::GetSymbol( const FormulaGrammar::Grammar eGrammar ) const +{ + ScCompiler aComp(rDoc, aPos, *pCode, eGrammar); + OUString symbol; + aComp.CreateStringFromTokenArray( symbol ); + return symbol; +} + +OUString ScRangeData::GetSymbol( const ScAddress& rPos, const FormulaGrammar::Grammar eGrammar ) const +{ + OUString aStr; + ScCompiler aComp(rDoc, rPos, *pCode, eGrammar); + aComp.CreateStringFromTokenArray( aStr ); + return aStr; +} + +void ScRangeData::UpdateSymbol( OUStringBuffer& rBuffer, const ScAddress& rPos ) +{ + ScTokenArray aTemp( pCode->CloneValue() ); + ScCompiler aComp(rDoc, rPos, aTemp, formula::FormulaGrammar::GRAM_DEFAULT); + aComp.MoveRelWrap(); + aComp.CreateStringFromTokenArray( rBuffer ); +} + +void ScRangeData::UpdateReference( sc::RefUpdateContext& rCxt, SCTAB nLocalTab ) +{ + sc::RefUpdateResult aRes = pCode->AdjustReferenceInName(rCxt, aPos); + bModified = aRes.mbReferenceModified; + if (aRes.mbReferenceModified) + rCxt.maUpdatedNames.setUpdatedName(nLocalTab, nIndex); +} + +void ScRangeData::UpdateTranspose( const ScRange& rSource, const ScAddress& rDest ) +{ + bool bChanged = false; + + formula::FormulaToken* t; + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + + while ( ( t = aIter.GetNextReference() ) != nullptr ) + { + if( t->GetType() != svIndex ) + { + SingleDoubleRefModifier aMod( *t ); + ScComplexRefData& rRef = aMod.Ref(); + // Update only absolute references + if (!rRef.Ref1.IsColRel() && !rRef.Ref1.IsRowRel() && + (!rRef.Ref1.IsFlag3D() || !rRef.Ref1.IsTabRel()) && + ( t->GetType() == svSingleRef || + (!rRef.Ref2.IsColRel() && !rRef.Ref2.IsRowRel() && + (!rRef.Ref2.IsFlag3D() || !rRef.Ref2.IsTabRel())))) + { + ScRange aAbs = rRef.toAbs(rDoc, aPos); + // Check if the absolute reference of this range is pointing to the transposed source + if (ScRefUpdate::UpdateTranspose(rDoc, rSource, rDest, aAbs) != UR_NOTHING) + { + rRef.SetRange(rDoc.GetSheetLimits(), aAbs, aPos); + bChanged = true; + } + } + } + } + + bModified = bChanged; +} + +void ScRangeData::UpdateGrow( const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY ) +{ + bool bChanged = false; + + formula::FormulaToken* t; + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + + while ( ( t = aIter.GetNextReference() ) != nullptr ) + { + if( t->GetType() != svIndex ) + { + SingleDoubleRefModifier aMod( *t ); + ScComplexRefData& rRef = aMod.Ref(); + if (!rRef.Ref1.IsColRel() && !rRef.Ref1.IsRowRel() && + (!rRef.Ref1.IsFlag3D() || !rRef.Ref1.IsTabRel()) && + ( t->GetType() == svSingleRef || + (!rRef.Ref2.IsColRel() && !rRef.Ref2.IsRowRel() && + (!rRef.Ref2.IsFlag3D() || !rRef.Ref2.IsTabRel())))) + { + ScRange aAbs = rRef.toAbs(rDoc, aPos); + if (ScRefUpdate::UpdateGrow(rArea, nGrowX, nGrowY, aAbs) != UR_NOTHING) + { + rRef.SetRange(rDoc.GetSheetLimits(), aAbs, aPos); + bChanged = true; + } + } + } + } + + bModified = bChanged; // has to be evaluated immediately afterwards +} + +bool ScRangeData::operator== (const ScRangeData& rData) const // for Undo +{ + if ( nIndex != rData.nIndex || + aName != rData.aName || + aPos != rData.aPos || + eType != rData.eType ) return false; + + sal_uInt16 nLen = pCode->GetLen(); + if ( nLen != rData.pCode->GetLen() ) return false; + + FormulaToken** ppThis = pCode->GetArray(); + FormulaToken** ppOther = rData.pCode->GetArray(); + + for ( sal_uInt16 i=0; i<nLen; i++ ) + if ( ppThis[i] != ppOther[i] && !(*ppThis[i] == *ppOther[i]) ) + return false; + + return true; +} + +bool ScRangeData::IsRangeAtBlock( const ScRange& rBlock ) const +{ + bool bRet = false; + ScRange aRange; + if ( IsReference(aRange) ) + bRet = ( rBlock == aRange ); + return bRet; +} + +bool ScRangeData::IsReference( ScRange& rRange ) const +{ + if ( (eType & ( Type::AbsArea | Type::RefArea | Type::AbsPos )) && pCode ) + return pCode->IsReference(rRange, aPos); + + return false; +} + +bool ScRangeData::IsReference( ScRange& rRange, const ScAddress& rPos ) const +{ + if ( (eType & ( Type::AbsArea | Type::RefArea | Type::AbsPos ) ) && pCode ) + return pCode->IsReference(rRange, rPos); + + return false; +} + +bool ScRangeData::IsValidReference( ScRange& rRange ) const +{ + if ( (eType & ( Type::AbsArea | Type::RefArea | Type::AbsPos ) ) && pCode ) + return pCode->IsValidReference(rRange, aPos); + + return false; +} + +void ScRangeData::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt, SCTAB nLocalTab ) +{ + sc::RefUpdateResult aRes = pCode->AdjustReferenceOnInsertedTab(rCxt, aPos); + if (aRes.mbReferenceModified) + rCxt.maUpdatedNames.setUpdatedName(nLocalTab, nIndex); + + if (rCxt.mnInsertPos <= aPos.Tab()) + aPos.IncTab(rCxt.mnSheets); +} + +void ScRangeData::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt, SCTAB nLocalTab ) +{ + sc::RefUpdateResult aRes = pCode->AdjustReferenceOnDeletedTab(rCxt, aPos); + if (aRes.mbReferenceModified) + rCxt.maUpdatedNames.setUpdatedName(nLocalTab, nIndex); + + ScRangeUpdater::UpdateDeleteTab( aPos, rCxt); +} + +void ScRangeData::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt, SCTAB nLocalTab ) +{ + sc::RefUpdateResult aRes = pCode->AdjustReferenceOnMovedTab(rCxt, aPos); + if (aRes.mbReferenceModified) + rCxt.maUpdatedNames.setUpdatedName(nLocalTab, nIndex); + + aPos.SetTab(rCxt.getNewTab(aPos.Tab())); +} + +void ScRangeData::MakeValidName( const ScDocument& rDoc, OUString& rName ) +{ + + // strip leading invalid characters + sal_Int32 nPos = 0; + sal_Int32 nLen = rName.getLength(); + while ( nPos < nLen && !ScCompiler::IsCharFlagAllConventions( rName, nPos, ScCharFlags::Name) ) + ++nPos; + if ( nPos>0 ) + rName = rName.copy(nPos); + + // if the first character is an invalid start character, precede with '_' + if ( !rName.isEmpty() && !ScCompiler::IsCharFlagAllConventions( rName, 0, ScCharFlags::CharName ) ) + rName = "_" + rName; + + // replace invalid with '_' + nLen = rName.getLength(); + for (nPos=0; nPos<nLen; nPos++) + { + if ( !ScCompiler::IsCharFlagAllConventions( rName, nPos, ScCharFlags::Name) ) + rName = rName.replaceAt( nPos, 1, u"_" ); + } + + // Ensure that the proposed name is not a reference under any convention, + // same as in IsNameValid() + ScAddress aAddr; + ScRange aRange; + for (int nConv = FormulaGrammar::CONV_UNSPECIFIED; ++nConv < FormulaGrammar::CONV_LAST; ) + { + ScAddress::Details details( static_cast<FormulaGrammar::AddressConvention>( nConv ) ); + // Don't check Parse on VALID, any partial only VALID may result in + // #REF! during compile later! + while (aRange.Parse(rName, rDoc, details) != ScRefFlags::ZERO || + aAddr.Parse(rName, rDoc, details) != ScRefFlags::ZERO) + { + // Range Parse is partially valid also with invalid sheet name, + // Address Parse ditto, during compile name would generate a #REF! + if ( rName.indexOf( '.' ) != -1 ) + rName = rName.replaceFirst( ".", "_" ); + else + rName = "_" + rName; + } + } +} + +ScRangeData::IsNameValidType ScRangeData::IsNameValid( const OUString& rName, const ScDocument& rDoc ) +{ + /* XXX If changed, sc/source/filter/ftools/ftools.cxx + * ScfTools::ConvertToScDefinedName needs to be changed too. */ + char const a('.'); + if (rName.indexOf(a) != -1) + return IsNameValidType::NAME_INVALID_BAD_STRING; + sal_Int32 nPos = 0; + sal_Int32 nLen = rName.getLength(); + if ( !nLen || !ScCompiler::IsCharFlagAllConventions( rName, nPos++, ScCharFlags::CharName ) ) + return IsNameValidType::NAME_INVALID_BAD_STRING; + while ( nPos < nLen ) + { + if ( !ScCompiler::IsCharFlagAllConventions( rName, nPos++, ScCharFlags::Name ) ) + return IsNameValidType::NAME_INVALID_BAD_STRING; + } + ScAddress aAddr; + ScRange aRange; + for (int nConv = FormulaGrammar::CONV_UNSPECIFIED; ++nConv < FormulaGrammar::CONV_LAST; ) + { + ScAddress::Details details( static_cast<FormulaGrammar::AddressConvention>( nConv ) ); + // Don't check Parse on VALID, any partial only VALID may result in + // #REF! during compile later! + if (aRange.Parse(rName, rDoc, details) != ScRefFlags::ZERO || + aAddr.Parse(rName, rDoc, details) != ScRefFlags::ZERO ) + { + return IsNameValidType::NAME_INVALID_CELL_REF; + } + } + return IsNameValidType::NAME_VALID; +} + +bool ScRangeData::HasPossibleAddressConflict() const +{ + // Similar to part of IsNameValid(), but only check if the name is a valid address. + ScAddress aAddr; + for (int nConv = FormulaGrammar::CONV_UNSPECIFIED; ++nConv < FormulaGrammar::CONV_LAST; ) + { + ScAddress::Details details( static_cast<FormulaGrammar::AddressConvention>( nConv ) ); + // Don't check Parse on VALID, any partial only VALID may result in + // #REF! during compile later! + if(aAddr.Parse(aUpperName, rDoc, details) != ScRefFlags::ZERO) + return true; + } + return false; +} + +FormulaError ScRangeData::GetErrCode() const +{ + return pCode ? pCode->GetCodeError() : FormulaError::NONE; +} + +bool ScRangeData::HasReferences() const +{ + return pCode->HasReferences(); +} + +sal_uInt32 ScRangeData::GetUnoType() const +{ + sal_uInt32 nUnoType = 0; + if ( HasType(Type::Criteria) ) nUnoType |= css::sheet::NamedRangeFlag::FILTER_CRITERIA; + if ( HasType(Type::PrintArea) ) nUnoType |= css::sheet::NamedRangeFlag::PRINT_AREA; + if ( HasType(Type::ColHeader) ) nUnoType |= css::sheet::NamedRangeFlag::COLUMN_HEADER; + if ( HasType(Type::RowHeader) ) nUnoType |= css::sheet::NamedRangeFlag::ROW_HEADER; + if ( HasType(Type::Hidden) ) nUnoType |= css::sheet::NamedRangeFlag::HIDDEN; + return nUnoType; +} + +void ScRangeData::ValidateTabRefs() +{ + // try to make sure all relative references and the reference position + // are within existing tables, so they can be represented as text + // (if the range of used tables is more than the existing tables, + // the result may still contain invalid tables, because the relative + // references aren't changed so formulas stay the same) + + // find range of used tables + + SCTAB nMinTab = aPos.Tab(); + SCTAB nMaxTab = nMinTab; + formula::FormulaToken* t; + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + while ( ( t = aIter.GetNextReference() ) != nullptr ) + { + ScSingleRefData& rRef1 = *t->GetSingleRef(); + ScAddress aAbs = rRef1.toAbs(rDoc, aPos); + if ( rRef1.IsTabRel() && !rRef1.IsTabDeleted() ) + { + if (aAbs.Tab() < nMinTab) + nMinTab = aAbs.Tab(); + if (aAbs.Tab() > nMaxTab) + nMaxTab = aAbs.Tab(); + } + if ( t->GetType() == svDoubleRef ) + { + ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2; + aAbs = rRef2.toAbs(rDoc, aPos); + if ( rRef2.IsTabRel() && !rRef2.IsTabDeleted() ) + { + if (aAbs.Tab() < nMinTab) + nMinTab = aAbs.Tab(); + if (aAbs.Tab() > nMaxTab) + nMaxTab = aAbs.Tab(); + } + } + } + + SCTAB nTabCount = rDoc.GetTableCount(); + if ( nMaxTab < nTabCount || nMinTab <= 0 ) + return; + + // move position and relative tab refs + // The formulas that use the name are not changed by this + + SCTAB nMove = nMinTab; + ScAddress aOldPos = aPos; + aPos.SetTab( aPos.Tab() - nMove ); + + aIter.Reset(); + while ( ( t = aIter.GetNextReference() ) != nullptr ) + { + switch (t->GetType()) + { + case svSingleRef: + { + ScSingleRefData& rRef = *t->GetSingleRef(); + if (!rRef.IsTabDeleted()) + { + ScAddress aAbs = rRef.toAbs(rDoc, aOldPos); + rRef.SetAddress(rDoc.GetSheetLimits(), aAbs, aPos); + } + } + break; + case svDoubleRef: + { + ScComplexRefData& rRef = *t->GetDoubleRef(); + if (!rRef.Ref1.IsTabDeleted()) + { + ScAddress aAbs = rRef.Ref1.toAbs(rDoc, aOldPos); + rRef.Ref1.SetAddress(rDoc.GetSheetLimits(), aAbs, aPos); + } + if (!rRef.Ref2.IsTabDeleted()) + { + ScAddress aAbs = rRef.Ref2.toAbs(rDoc, aOldPos); + rRef.Ref2.SetAddress(rDoc.GetSheetLimits(), aAbs, aPos); + } + } + break; + default: + ; + } + } +} + +void ScRangeData::SetCode( const ScTokenArray& rArr ) +{ + pCode.reset(new ScTokenArray( rArr )); + pCode->SetFromRangeName(true); + InitCode(); +} + +void ScRangeData::InitCode() +{ + if( pCode->GetCodeError() == FormulaError::NONE ) + { + FormulaToken* p = FormulaTokenArrayPlainIterator(*pCode).GetNextReference(); + if( p ) // exact one reference at first + { + if( p->GetType() == svSingleRef ) + eType = eType | Type::AbsPos; + else + eType = eType | Type::AbsArea; + } + } +} + +extern "C" +int ScRangeData_QsortNameCompare( const void* p1, const void* p2 ) +{ + return static_cast<int>(ScGlobal::GetCollator().compareString( + (*static_cast<const ScRangeData* const *>(p1))->GetName(), + (*static_cast<const ScRangeData* const *>(p2))->GetName() )); +} + +namespace { + +/** + * Predicate to check if the name references the specified range. + */ +class MatchByRange +{ + const ScRange& mrRange; +public: + explicit MatchByRange(const ScRange& rRange) : mrRange(rRange) {} + bool operator() (std::pair<OUString const, std::unique_ptr<ScRangeData>> const& r) const + { + return r.second->IsRangeAtBlock(mrRange); + } +}; + +} + +ScRangeName::ScRangeName() + : mHasPossibleAddressConflict(false) + , mHasPossibleAddressConflictDirty(false) +{ +} + +ScRangeName::ScRangeName(const ScRangeName& r) + : mHasPossibleAddressConflict( r.mHasPossibleAddressConflict ) + , mHasPossibleAddressConflictDirty( r.mHasPossibleAddressConflictDirty ) +{ + for (auto const& it : r.m_Data) + { + m_Data.insert(std::make_pair(it.first, std::make_unique<ScRangeData>(*it.second))); + } + // std::map was cloned, so each collection needs its own index to data. + maIndexToData.resize( r.maIndexToData.size(), nullptr); + for (auto const& itr : m_Data) + { + size_t nPos = itr.second->GetIndex() - 1; + if (nPos >= maIndexToData.size()) + { + OSL_FAIL( "ScRangeName copy-ctor: maIndexToData size doesn't fit"); + maIndexToData.resize(nPos+1, nullptr); + } + maIndexToData[nPos] = itr.second.get(); + } +} + +const ScRangeData* ScRangeName::findByRange(const ScRange& rRange) const +{ + DataType::const_iterator itr = std::find_if( + m_Data.begin(), m_Data.end(), MatchByRange(rRange)); + return itr == m_Data.end() ? nullptr : itr->second.get(); +} + +ScRangeData* ScRangeName::findByUpperName(const OUString& rName) +{ + DataType::iterator itr = m_Data.find(rName); + return itr == m_Data.end() ? nullptr : itr->second.get(); +} + +const ScRangeData* ScRangeName::findByUpperName(const OUString& rName) const +{ + DataType::const_iterator itr = m_Data.find(rName); + return itr == m_Data.end() ? nullptr : itr->second.get(); +} + +ScRangeData* ScRangeName::findByIndex(sal_uInt16 i) const +{ + if (!i) + // index should never be zero. + return nullptr; + + size_t nPos = i - 1; + return nPos < maIndexToData.size() ? maIndexToData[nPos] : nullptr; +} + +void ScRangeName::UpdateReference(sc::RefUpdateContext& rCxt, SCTAB nLocalTab ) +{ + if (rCxt.meMode == URM_COPY) + // Copying cells does not modify named expressions. + return; + + for (auto const& itr : m_Data) + { + itr.second->UpdateReference(rCxt, nLocalTab); + } +} + +void ScRangeName::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt, SCTAB nLocalTab ) +{ + for (auto const& itr : m_Data) + { + itr.second->UpdateInsertTab(rCxt, nLocalTab); + } +} + +void ScRangeName::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt, SCTAB nLocalTab ) +{ + for (auto const& itr : m_Data) + { + itr.second->UpdateDeleteTab(rCxt, nLocalTab); + } +} + +void ScRangeName::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt, SCTAB nLocalTab ) +{ + for (auto const& itr : m_Data) + { + itr.second->UpdateMoveTab(rCxt, nLocalTab); + } +} + +void ScRangeName::UpdateTranspose(const ScRange& rSource, const ScAddress& rDest) +{ + for (auto const& itr : m_Data) + { + itr.second->UpdateTranspose(rSource, rDest); + } +} + +void ScRangeName::UpdateGrow(const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY) +{ + for (auto const& itr : m_Data) + { + itr.second->UpdateGrow(rArea, nGrowX, nGrowY); + } +} + +void ScRangeName::CompileUnresolvedXML( sc::CompileFormulaContext& rCxt ) +{ + for (auto const& itr : m_Data) + { + itr.second->CompileUnresolvedXML(rCxt); + } +} + +void ScRangeName::CopyUsedNames( const SCTAB nLocalTab, const SCTAB nOldTab, const SCTAB nNewTab, + const ScDocument& rOldDoc, ScDocument& rNewDoc, const bool bGlobalNamesToLocal ) const +{ + for (auto const& itr : m_Data) + { + SCTAB nSheet = (nLocalTab < 0) ? nLocalTab : nOldTab; + sal_uInt16 nIndex = itr.second->GetIndex(); + ScAddress aOldPos( itr.second->GetPos()); + aOldPos.SetTab( nOldTab); + ScAddress aNewPos( aOldPos); + aNewPos.SetTab( nNewTab); + ScRangeData* pRangeData = nullptr; + rOldDoc.CopyAdjustRangeName( nSheet, nIndex, pRangeData, rNewDoc, aNewPos, aOldPos, bGlobalNamesToLocal, false); + } +} + +bool ScRangeName::insert( ScRangeData* p, bool bReuseFreeIndex ) +{ + if (!p) + return false; + + if (!p->GetIndex()) + { + // Assign a new index. An index must be unique and is never 0. + if (bReuseFreeIndex) + { + IndexDataType::iterator itr = std::find( + maIndexToData.begin(), maIndexToData.end(), static_cast<ScRangeData*>(nullptr)); + if (itr != maIndexToData.end()) + { + // Empty slot exists. Re-use it. + size_t nPos = std::distance(maIndexToData.begin(), itr); + p->SetIndex(nPos + 1); + } + else + // No empty slot. Append it to the end. + p->SetIndex(maIndexToData.size() + 1); + } + else + { + p->SetIndex(maIndexToData.size() + 1); + } + } + + OUString aName(p->GetUpperName()); + erase(aName); // ptr_map won't insert it if a duplicate name exists. + pair<DataType::iterator, bool> r = + m_Data.insert(std::make_pair(aName, std::unique_ptr<ScRangeData>(p))); + if (r.second) + { + // Data inserted. Store its index for mapping. + size_t nPos = p->GetIndex() - 1; + if (nPos >= maIndexToData.size()) + maIndexToData.resize(nPos+1, nullptr); + maIndexToData[nPos] = p; + mHasPossibleAddressConflictDirty = true; + } + return r.second; +} + +void ScRangeName::erase(const ScRangeData& r) +{ + erase(r.GetUpperName()); +} + +void ScRangeName::erase(const OUString& rName) +{ + DataType::const_iterator itr = m_Data.find(rName); + if (itr != m_Data.end()) + erase(itr); +} + +void ScRangeName::erase(const_iterator itr) +{ + sal_uInt16 nIndex = itr->second->GetIndex(); + m_Data.erase(itr); + OSL_ENSURE( 0 < nIndex && nIndex <= maIndexToData.size(), "ScRangeName::erase: bad index"); + if (0 < nIndex && nIndex <= maIndexToData.size()) + maIndexToData[nIndex-1] = nullptr; + if(mHasPossibleAddressConflict) + mHasPossibleAddressConflictDirty = true; +} + +void ScRangeName::clear() +{ + m_Data.clear(); + maIndexToData.clear(); + mHasPossibleAddressConflict = false; + mHasPossibleAddressConflictDirty = false; +} + +void ScRangeName::checkHasPossibleAddressConflict() const +{ + mHasPossibleAddressConflict = false; + mHasPossibleAddressConflictDirty = false; + for (auto const& itr : m_Data) + { + if( itr.second->HasPossibleAddressConflict()) + { + mHasPossibleAddressConflict = true; + return; + } + } +} + +bool ScRangeName::operator== (const ScRangeName& r) const +{ + return std::equal(m_Data.begin(), m_Data.end(), r.m_Data.begin(), r.m_Data.end(), + [](const DataType::value_type& lhs, const DataType::value_type& rhs) { + return (lhs.first == rhs.first) && (*lhs.second == *rhs.second); + }); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/rangeseq.cxx b/sc/source/core/tool/rangeseq.cxx new file mode 100644 index 0000000000..75652b720a --- /dev/null +++ b/sc/source/core/tool/rangeseq.cxx @@ -0,0 +1,451 @@ +/* -*- 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 <svl/numformat.hxx> +#include <rtl/math.hxx> +#include <o3tl/float_int_conversion.hxx> +#include <osl/diagnose.h> +#include <osl/thread.h> + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <comphelper/string.hxx> +#include <rangeseq.hxx> +#include <document.hxx> +#include <dociter.hxx> +#include <scmatrix.hxx> +#include <formulacell.hxx> + +using namespace com::sun::star; + +static bool lcl_HasErrors( ScDocument& rDoc, const ScRange& rRange ) +{ + // no need to look at empty cells - just use ScCellIterator + ScCellIterator aIter( rDoc, rRange ); + for (bool bHas = aIter.first(); bHas; bHas = aIter.next()) + { + if (aIter.getType() != CELLTYPE_FORMULA) + continue; + + ScFormulaCell* pCell = aIter.getFormulaCell(); + if (pCell->GetErrCode() != FormulaError::NONE) + return true; + } + return false; // no error found +} + +static tools::Long lcl_DoubleToLong( double fVal ) +{ + double fInt = (fVal >= 0.0) ? ::rtl::math::approxFloor( fVal ) : + ::rtl::math::approxCeil( fVal ); + if ( o3tl::convertsToAtLeast(fInt, LONG_MIN) && o3tl::convertsToAtMost(fInt, LONG_MAX) ) + return static_cast<tools::Long>(fInt); + else + return 0; // out of range +} + +bool ScRangeToSequence::FillLongArray( uno::Any& rAny, ScDocument& rDoc, const ScRange& rRange ) +{ + SCTAB nTab = rRange.aStart.Tab(); + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + sal_Int32 nColCount = rRange.aEnd.Col() + 1 - rRange.aStart.Col(); + sal_Int32 nRowCount = rRange.aEnd.Row() + 1 - rRange.aStart.Row(); + + uno::Sequence< uno::Sequence<sal_Int32> > aRowSeq( nRowCount ); + uno::Sequence<sal_Int32>* pRowAry = aRowSeq.getArray(); + for (sal_Int32 nRow = 0; nRow < nRowCount; nRow++) + { + uno::Sequence<sal_Int32> aColSeq( nColCount ); + sal_Int32* pColAry = aColSeq.getArray(); + for (sal_Int32 nCol = 0; nCol < nColCount; nCol++) + pColAry[nCol] = lcl_DoubleToLong( rDoc.GetValue( + ScAddress( static_cast<SCCOL>(nStartCol+nCol), static_cast<SCROW>(nStartRow+nRow), nTab ) ) ); + + pRowAry[nRow] = aColSeq; + } + + rAny <<= aRowSeq; + return !lcl_HasErrors( rDoc, rRange ); +} + +bool ScRangeToSequence::FillLongArray( uno::Any& rAny, const ScMatrix* pMatrix ) +{ + if (!pMatrix) + return false; + + SCSIZE nColCount; + SCSIZE nRowCount; + pMatrix->GetDimensions( nColCount, nRowCount ); + + uno::Sequence< uno::Sequence<sal_Int32> > aRowSeq( static_cast<sal_Int32>(nRowCount) ); + uno::Sequence<sal_Int32>* pRowAry = aRowSeq.getArray(); + for (SCSIZE nRow = 0; nRow < nRowCount; nRow++) + { + uno::Sequence<sal_Int32> aColSeq( static_cast<sal_Int32>(nColCount) ); + sal_Int32* pColAry = aColSeq.getArray(); + for (SCSIZE nCol = 0; nCol < nColCount; nCol++) + if ( pMatrix->IsStringOrEmpty( nCol, nRow ) ) + pColAry[nCol] = 0; + else + pColAry[nCol] = lcl_DoubleToLong( pMatrix->GetDouble( nCol, nRow ) ); + + pRowAry[nRow] = aColSeq; + } + + rAny <<= aRowSeq; + return true; +} + +bool ScRangeToSequence::FillDoubleArray( uno::Any& rAny, ScDocument& rDoc, const ScRange& rRange ) +{ + SCTAB nTab = rRange.aStart.Tab(); + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + sal_Int32 nColCount = rRange.aEnd.Col() + 1 - rRange.aStart.Col(); + sal_Int32 nRowCount = rRange.aEnd.Row() + 1 - rRange.aStart.Row(); + + uno::Sequence< uno::Sequence<double> > aRowSeq( nRowCount ); + uno::Sequence<double>* pRowAry = aRowSeq.getArray(); + for (sal_Int32 nRow = 0; nRow < nRowCount; nRow++) + { + uno::Sequence<double> aColSeq( nColCount ); + double* pColAry = aColSeq.getArray(); + for (sal_Int32 nCol = 0; nCol < nColCount; nCol++) + pColAry[nCol] = rDoc.GetValue( + ScAddress( static_cast<SCCOL>(nStartCol+nCol), static_cast<SCROW>(nStartRow+nRow), nTab ) ); + + pRowAry[nRow] = aColSeq; + } + + rAny <<= aRowSeq; + return !lcl_HasErrors( rDoc, rRange ); +} + +bool ScRangeToSequence::FillDoubleArray( uno::Any& rAny, const ScMatrix* pMatrix ) +{ + if (!pMatrix) + return false; + + SCSIZE nColCount; + SCSIZE nRowCount; + pMatrix->GetDimensions( nColCount, nRowCount ); + + uno::Sequence< uno::Sequence<double> > aRowSeq( static_cast<sal_Int32>(nRowCount) ); + uno::Sequence<double>* pRowAry = aRowSeq.getArray(); + for (SCSIZE nRow = 0; nRow < nRowCount; nRow++) + { + uno::Sequence<double> aColSeq( static_cast<sal_Int32>(nColCount) ); + double* pColAry = aColSeq.getArray(); + for (SCSIZE nCol = 0; nCol < nColCount; nCol++) + if ( pMatrix->IsStringOrEmpty( nCol, nRow ) ) + pColAry[nCol] = 0.0; + else + pColAry[nCol] = pMatrix->GetDouble( nCol, nRow ); + + pRowAry[nRow] = aColSeq; + } + + rAny <<= aRowSeq; + return true; +} + +bool ScRangeToSequence::FillStringArray( uno::Any& rAny, ScDocument& rDoc, const ScRange& rRange ) +{ + SCTAB nTab = rRange.aStart.Tab(); + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + sal_Int32 nColCount = rRange.aEnd.Col() + 1 - rRange.aStart.Col(); + sal_Int32 nRowCount = rRange.aEnd.Row() + 1 - rRange.aStart.Row(); + + bool bHasErrors = false; + + uno::Sequence< uno::Sequence<OUString> > aRowSeq( nRowCount ); + uno::Sequence<OUString>* pRowAry = aRowSeq.getArray(); + for (sal_Int32 nRow = 0; nRow < nRowCount; nRow++) + { + uno::Sequence<OUString> aColSeq( nColCount ); + OUString* pColAry = aColSeq.getArray(); + for (sal_Int32 nCol = 0; nCol < nColCount; nCol++) + { + FormulaError nErrCode = rDoc.GetStringForFormula( + ScAddress(static_cast<SCCOL>(nStartCol+nCol), static_cast<SCROW>(nStartRow+nRow), nTab), + pColAry[nCol] ); + if ( nErrCode != FormulaError::NONE ) + bHasErrors = true; + } + pRowAry[nRow] = aColSeq; + } + + rAny <<= aRowSeq; + return !bHasErrors; +} + +bool ScRangeToSequence::FillStringArray( uno::Any& rAny, const ScMatrix* pMatrix, + SvNumberFormatter* pFormatter ) +{ + if (!pMatrix) + return false; + + SCSIZE nColCount; + SCSIZE nRowCount; + pMatrix->GetDimensions( nColCount, nRowCount ); + + uno::Sequence< uno::Sequence<OUString> > aRowSeq( static_cast<sal_Int32>(nRowCount) ); + uno::Sequence<OUString>* pRowAry = aRowSeq.getArray(); + for (SCSIZE nRow = 0; nRow < nRowCount; nRow++) + { + uno::Sequence<OUString> aColSeq( static_cast<sal_Int32>(nColCount) ); + OUString* pColAry = aColSeq.getArray(); + for (SCSIZE nCol = 0; nCol < nColCount; nCol++) + { + OUString aStr; + if ( pMatrix->IsStringOrEmpty( nCol, nRow ) ) + { + if ( !pMatrix->IsEmpty( nCol, nRow ) ) + aStr = pMatrix->GetString(nCol, nRow).getString(); + } + else if ( pFormatter ) + { + double fVal = pMatrix->GetDouble( nCol, nRow ); + const Color* pColor; + pFormatter->GetOutputString( fVal, 0, aStr, &pColor ); + } + pColAry[nCol] = aStr; + } + + pRowAry[nRow] = aColSeq; + } + + rAny <<= aRowSeq; + return true; +} + +bool ScRangeToSequence::FillMixedArray( uno::Any& rAny, ScDocument& rDoc, const ScRange& rRange, + bool bAllowNV ) +{ + SCTAB nTab = rRange.aStart.Tab(); + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + sal_Int32 nColCount = rRange.aEnd.Col() + 1 - rRange.aStart.Col(); + sal_Int32 nRowCount = rRange.aEnd.Row() + 1 - rRange.aStart.Row(); + + bool bHasErrors = false; + + uno::Sequence< uno::Sequence<uno::Any> > aRowSeq( nRowCount ); + uno::Sequence<uno::Any>* pRowAry = aRowSeq.getArray(); + for (sal_Int32 nRow = 0; nRow < nRowCount; nRow++) + { + uno::Sequence<uno::Any> aColSeq( nColCount ); + uno::Any* pColAry = aColSeq.getArray(); + for (sal_Int32 nCol = 0; nCol < nColCount; nCol++) + { + uno::Any& rElement = pColAry[nCol]; + + ScAddress aPos( static_cast<SCCOL>(nStartCol+nCol), static_cast<SCROW>(nStartRow+nRow), nTab ); + ScRefCellValue aCell(rDoc, aPos); + + if (aCell.isEmpty()) + { + rElement <<= OUString(); + continue; + } + + if (aCell.getType() == CELLTYPE_FORMULA && aCell.getFormula()->GetErrCode() != FormulaError::NONE) + { + // if NV is allowed, leave empty for errors + bHasErrors = true; + } + else if (aCell.hasNumeric()) + rElement <<= aCell.getValue(); + else + rElement <<= aCell.getString(&rDoc); + } + pRowAry[nRow] = aColSeq; + } + + rAny <<= aRowSeq; + return bAllowNV || !bHasErrors; +} + +bool ScRangeToSequence::FillMixedArray( uno::Any& rAny, const ScMatrix* pMatrix, bool bDataTypes ) +{ + if (!pMatrix) + return false; + + SCSIZE nColCount; + SCSIZE nRowCount; + pMatrix->GetDimensions( nColCount, nRowCount ); + + uno::Sequence< uno::Sequence<uno::Any> > aRowSeq( static_cast<sal_Int32>(nRowCount) ); + uno::Sequence<uno::Any>* pRowAry = aRowSeq.getArray(); + for (SCSIZE nRow = 0; nRow < nRowCount; nRow++) + { + uno::Sequence<uno::Any> aColSeq( static_cast<sal_Int32>(nColCount) ); + uno::Any* pColAry = aColSeq.getArray(); + for (SCSIZE nCol = 0; nCol < nColCount; nCol++) + { + if ( pMatrix->IsStringOrEmpty( nCol, nRow ) ) + { + OUString aStr; + if ( !pMatrix->IsEmpty( nCol, nRow ) ) + aStr = pMatrix->GetString(nCol, nRow).getString(); + pColAry[nCol] <<= aStr; + } + else + { + double fVal = pMatrix->GetDouble( nCol, nRow ); + if (bDataTypes && pMatrix->IsBoolean( nCol, nRow )) + pColAry[nCol] <<= fVal != 0.0; + else + pColAry[nCol] <<= fVal; + } + } + + pRowAry[nRow] = aColSeq; + } + + rAny <<= aRowSeq; + return true; +} + +bool ScApiTypeConversion::ConvertAnyToDouble( double & o_fVal, + css::uno::TypeClass & o_eClass, + const css::uno::Any & rAny ) +{ + bool bRet = false; + o_eClass = rAny.getValueTypeClass(); + switch (o_eClass) + { + //TODO: extract integer values + case uno::TypeClass_ENUM: + case uno::TypeClass_BOOLEAN: + case uno::TypeClass_CHAR: + case uno::TypeClass_BYTE: + case uno::TypeClass_SHORT: + case uno::TypeClass_UNSIGNED_SHORT: + case uno::TypeClass_LONG: + case uno::TypeClass_UNSIGNED_LONG: + case uno::TypeClass_FLOAT: + case uno::TypeClass_DOUBLE: + rAny >>= o_fVal; + bRet = true; + break; + default: + ; // nothing, avoid warning + } + if (!bRet) + o_fVal = 0.0; + return bRet; +} + +ScMatrixRef ScSequenceToMatrix::CreateMixedMatrix( const css::uno::Any & rAny ) +{ + ScMatrixRef xMatrix; + uno::Sequence< uno::Sequence< uno::Any > > aSequence; + if ( rAny >>= aSequence ) + { + sal_Int32 nRowCount = aSequence.getLength(); + sal_Int32 nMaxColCount = 0; + if (nRowCount) + { + auto pRow = std::max_element(std::cbegin(aSequence), std::cend(aSequence), + [](const uno::Sequence<uno::Any>& a, const uno::Sequence<uno::Any>& b) { + return a.getLength() < b.getLength(); }); + nMaxColCount = pRow->getLength(); + } + if ( nMaxColCount && nRowCount ) + { + const uno::Sequence<uno::Any>* pRowArr = aSequence.getConstArray(); + OUString aUStr; + xMatrix = new ScMatrix( + static_cast<SCSIZE>(nMaxColCount), + static_cast<SCSIZE>(nRowCount), 0.0); + SCSIZE nCols, nRows; + xMatrix->GetDimensions( nCols, nRows); + if (nCols != static_cast<SCSIZE>(nMaxColCount) || nRows != static_cast<SCSIZE>(nRowCount)) + { + OSL_FAIL( "ScSequenceToMatrix::CreateMixedMatrix: matrix exceeded max size, returning NULL matrix"); + return nullptr; + } + for (sal_Int32 nRow=0; nRow<nRowCount; nRow++) + { + sal_Int32 nColCount = pRowArr[nRow].getLength(); + const uno::Any* pColArr = pRowArr[nRow].getConstArray(); + for (sal_Int32 nCol=0; nCol<nColCount; nCol++) + { + double fVal; + uno::TypeClass eClass; + if (ScApiTypeConversion::ConvertAnyToDouble( fVal, eClass, pColArr[nCol])) + { + if (eClass == uno::TypeClass_BOOLEAN) + xMatrix->PutBoolean( fVal != 0.0, + static_cast<SCSIZE>(nCol), + static_cast<SCSIZE>(nRow) ); + else + xMatrix->PutDouble( fVal, + static_cast<SCSIZE>(nCol), + static_cast<SCSIZE>(nRow) ); + } + else + { + // Try string, else use empty as last resort. + + if ( pColArr[nCol] >>= aUStr ) + { + xMatrix->PutString( + svl::SharedString(aUStr), static_cast<SCSIZE>(nCol), static_cast<SCSIZE>(nRow)); + } + else + xMatrix->PutEmpty( + static_cast<SCSIZE>(nCol), + static_cast<SCSIZE>(nRow) ); + } + } + for (sal_Int32 nCol=nColCount; nCol<nMaxColCount; nCol++) + { + xMatrix->PutEmpty( + static_cast<SCSIZE>(nCol), + static_cast<SCSIZE>(nRow) ); + } + } + } + } + return xMatrix; +} + +bool ScByteSequenceToString::GetString( OUString& rString, const uno::Any& rAny ) +{ + bool bResult = false; + if (rAny >>= rString) + { + bResult = true; + } + else if (uno::Sequence<sal_Int8> aSeq; rAny >>= aSeq) + { + rString = OUString( reinterpret_cast<const char*>(aSeq.getConstArray()), + aSeq.getLength(), osl_getThreadTextEncoding() ); + bResult = true; + } + if (bResult) + rString = comphelper::string::stripEnd(rString, 0); + return bResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/rangeutl.cxx b/sc/source/core/tool/rangeutl.cxx new file mode 100644 index 0000000000..e7314645c2 --- /dev/null +++ b/sc/source/core/tool/rangeutl.cxx @@ -0,0 +1,1067 @@ +/* -*- 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 <memory> +#include <osl/diagnose.h> +#include <unotools/charclass.hxx> +#include <rangeutl.hxx> +#include <document.hxx> +#include <global.hxx> +#include <dbdata.hxx> +#include <rangenam.hxx> +#include <convuno.hxx> +#include <externalrefmgr.hxx> +#include <compiler.hxx> +#include <refupdatecontext.hxx> + +using ::formula::FormulaGrammar; +using namespace ::com::sun::star; + +bool ScRangeUtil::MakeArea( const OUString& rAreaStr, + ScArea& rArea, + const ScDocument& rDoc, + SCTAB nTab, + ScAddress::Details const & rDetails ) +{ + // Input in rAreaStr: "$Tabelle1.$A1:$D17" + + // BROKEN BROKEN BROKEN + // but it is only used in the consolidate dialog. Ignore for now. + + bool bSuccess = false; + sal_Int32 nPointPos = rAreaStr.indexOf('.'); + sal_Int32 nColonPos = rAreaStr.indexOf(':'); + OUString aStrArea( rAreaStr ); + ScRefAddress startPos; + ScRefAddress endPos; + + if ( nColonPos == -1 && nPointPos != -1 ) + { + aStrArea += OUString::Concat(":") + rAreaStr.subView( nPointPos+1 ); // do not include '.' in copy + } + + bSuccess = ConvertDoubleRef( rDoc, aStrArea, nTab, startPos, endPos, rDetails ); + + if ( bSuccess ) + rArea = ScArea( startPos.Tab(), + startPos.Col(), startPos.Row(), + endPos.Col(), endPos.Row() ); + + return bSuccess; +} + +void ScRangeUtil::CutPosString( const OUString& theAreaStr, + OUString& thePosStr ) +{ + OUString aPosStr; + // BROKEN BROKEN BROKEN + // but it is only used in the consolidate dialog. Ignore for now. + + sal_Int32 nColonPos = theAreaStr.indexOf(':'); + + if ( nColonPos != -1 ) + aPosStr = theAreaStr.copy( 0, nColonPos ); // do not include ':' in copy + else + aPosStr = theAreaStr; + + thePosStr = aPosStr; +} + +bool ScRangeUtil::IsAbsTabArea( const OUString& rAreaStr, + const ScDocument* pDoc, + std::unique_ptr<ScArea[]>* ppAreas, + sal_uInt16* pAreaCount, + bool /* bAcceptCellRef */, + ScAddress::Details const & rDetails ) +{ + OSL_ENSURE( pDoc, "No document given!" ); + if ( !pDoc ) + return false; + + // BROKEN BROKEN BROKEN + // but it is only used in the consolidate dialog. Ignore for now. + + /* + * Expects strings like: + * "$Tabelle1.$A$1:$Tabelle3.$D$17" + * If bAcceptCellRef == sal_True then also accept strings like: + * "$Tabelle1.$A$1" + * + * as result a ScArea-Array is created, + * which is published via ppAreas and also has to be deleted this route. + */ + + bool bStrOk = false; + OUString aTempAreaStr(rAreaStr); + + if ( -1 == aTempAreaStr.indexOf(':') ) + { + aTempAreaStr += ":" + rAreaStr; + } + + sal_Int32 nColonPos = aTempAreaStr.indexOf(':'); + + if ( -1 != nColonPos + && -1 != aTempAreaStr.indexOf('.') ) + { + ScRefAddress aStartPos; + + OUString aStartPosStr = aTempAreaStr.copy( 0, nColonPos ); + OUString aEndPosStr = aTempAreaStr.copy( nColonPos+1 ); + + if ( ConvertSingleRef( *pDoc, aStartPosStr, 0, aStartPos, rDetails ) ) + { + ScRefAddress aEndPos; + if ( ConvertSingleRef( *pDoc, aEndPosStr, aStartPos.Tab(), aEndPos, rDetails ) ) + { + aStartPos.SetRelCol( false ); + aStartPos.SetRelRow( false ); + aStartPos.SetRelTab( false ); + aEndPos.SetRelCol( false ); + aEndPos.SetRelRow( false ); + aEndPos.SetRelTab( false ); + + bStrOk = true; + + if ( ppAreas && pAreaCount ) // Array returned ? + { + SCTAB nStartTab = aStartPos.Tab(); + SCTAB nEndTab = aEndPos.Tab(); + sal_uInt16 nTabCount = static_cast<sal_uInt16>(nEndTab-nStartTab+1); + ppAreas->reset(new ScArea[nTabCount]); + SCTAB nTab = 0; + sal_uInt16 i = 0; + ScArea theArea( 0, aStartPos.Col(), aStartPos.Row(), + aEndPos.Col(), aEndPos.Row() ); + + nTab = nStartTab; + for ( i=0; i<nTabCount; i++ ) + { + (*ppAreas)[i] = theArea; + (*ppAreas)[i].nTab = nTab; + nTab++; + } + *pAreaCount = nTabCount; + } + } + } + } + + return bStrOk; +} + +bool ScRangeUtil::IsAbsArea( const OUString& rAreaStr, + const ScDocument& rDoc, + SCTAB nTab, + OUString* pCompleteStr, + ScRefAddress* pStartPos, + ScRefAddress* pEndPos, + ScAddress::Details const & rDetails ) +{ + ScRefAddress startPos; + ScRefAddress endPos; + + bool bIsAbsArea = ConvertDoubleRef( rDoc, rAreaStr, nTab, startPos, endPos, rDetails ); + + if ( bIsAbsArea ) + { + startPos.SetRelCol( false ); + startPos.SetRelRow( false ); + startPos.SetRelTab( false ); + endPos .SetRelCol( false ); + endPos .SetRelRow( false ); + endPos .SetRelTab( false ); + + if ( pCompleteStr ) + { + *pCompleteStr = startPos.GetRefString( rDoc, MAXTAB+1, rDetails ); + *pCompleteStr += ":"; + *pCompleteStr += endPos.GetRefString( rDoc, nTab, rDetails ); + } + + if ( pStartPos && pEndPos ) + { + *pStartPos = startPos; + *pEndPos = endPos; + } + } + + return bIsAbsArea; +} + +bool ScRangeUtil::IsAbsPos( const OUString& rPosStr, + const ScDocument& rDoc, + SCTAB nTab, + OUString* pCompleteStr, + ScRefAddress* pPosTripel, + ScAddress::Details const & rDetails ) +{ + ScRefAddress thePos; + + bool bIsAbsPos = ConvertSingleRef( rDoc, rPosStr, nTab, thePos, rDetails ); + thePos.SetRelCol( false ); + thePos.SetRelRow( false ); + thePos.SetRelTab( false ); + + if ( bIsAbsPos ) + { + if ( pPosTripel ) + *pPosTripel = thePos; + if ( pCompleteStr ) + *pCompleteStr = thePos.GetRefString( rDoc, MAXTAB+1, rDetails ); + } + + return bIsAbsPos; +} + +bool ScRangeUtil::MakeRangeFromName ( + const OUString& rName, + const ScDocument& rDoc, + SCTAB nCurTab, + ScRange& rRange, + RutlNameScope eScope, + ScAddress::Details const & rDetails, + bool bUseDetailsPos ) +{ + bool bResult = false; + if (rName.isEmpty()) + return bResult; + + SCTAB nTab = 0; + SCCOL nColStart = 0; + SCCOL nColEnd = 0; + SCROW nRowStart = 0; + SCROW nRowEnd = 0; + + if (eScope == RUTL_NAMES || eScope == RUTL_NAMES_LOCAL || eScope == RUTL_NAMES_GLOBAL) + { + OUString aName(rName); + SCTAB nTable = nCurTab; + + if (eScope != RUTL_NAMES_GLOBAL) + { + // First handle UI names like "local1 (Sheet1)", which point to a + // local range name. + const sal_Int32 nEndPos = aName.getLength() - 1; + if (rName[nEndPos] == ')') + { + const sal_Int32 nStartPos = aName.indexOf(" ("); + if (nStartPos != -1) + { + OUString aSheetName = aName.copy(nStartPos+2, nEndPos-nStartPos-2); + if (rDoc.GetTable(aSheetName, nTable)) + { + aName = aName.copy(0, nStartPos); + eScope = RUTL_NAMES_LOCAL; + } + else + nTable = nCurTab; + } + } + } + + aName = ScGlobal::getCharClass().uppercase(aName); + ScRangeData* pData = nullptr; + if (eScope != RUTL_NAMES_GLOBAL) + { + // Check for local range names. + ScRangeName* pRangeNames = rDoc.GetRangeName( nTable ); + if ( pRangeNames ) + pData = pRangeNames->findByUpperName(aName); + } + if (!pData && eScope != RUTL_NAMES_LOCAL) + pData = rDoc.GetRangeName()->findByUpperName(aName); + if (pData) + { + OUString aStrArea; + ScRefAddress aStartPos; + ScRefAddress aEndPos; + + // tdf#138646: use the current grammar of the document and passed + // address convention. + // tdf#145077: create range string according to current cell cursor + // position if expression has relative references and details say so. + if (bUseDetailsPos) + aStrArea = pData->GetSymbol( ScAddress( rDetails.nCol, rDetails.nRow, nCurTab), + FormulaGrammar::mergeToGrammar(rDoc.GetGrammar(), rDetails.eConv)); + else + aStrArea = pData->GetSymbol( + FormulaGrammar::mergeToGrammar(rDoc.GetGrammar(), rDetails.eConv)); + + if ( IsAbsArea( aStrArea, rDoc, nTable, + nullptr, &aStartPos, &aEndPos, rDetails ) ) + { + nTab = aStartPos.Tab(); + nColStart = aStartPos.Col(); + nRowStart = aStartPos.Row(); + nColEnd = aEndPos.Col(); + nRowEnd = aEndPos.Row(); + bResult = true; + } + else + { + CutPosString( aStrArea, aStrArea ); + + if ( IsAbsPos( aStrArea, rDoc, nTable, + nullptr, &aStartPos, rDetails ) ) + { + nTab = aStartPos.Tab(); + nColStart = nColEnd = aStartPos.Col(); + nRowStart = nRowEnd = aStartPos.Row(); + bResult = true; + } + } + } + } + else if( eScope==RUTL_DBASE ) + { + ScDBCollection::NamedDBs& rDbNames = rDoc.GetDBCollection()->getNamedDBs(); + ScDBData* pData = rDbNames.findByUpperName(ScGlobal::getCharClass().uppercase(rName)); + if (pData) + { + pData->GetArea(nTab, nColStart, nRowStart, nColEnd, nRowEnd); + bResult = true; + } + } + else + { + OSL_FAIL( "ScRangeUtil::MakeRangeFromName" ); + } + + if( bResult ) + { + rRange = ScRange( nColStart, nRowStart, nTab, nColEnd, nRowEnd, nTab ); + } + + return bResult; +} + +void ScRangeStringConverter::AssignString( + OUString& rString, + const OUString& rNewStr, + bool bAppendStr, + sal_Unicode cSeparator) +{ + if( bAppendStr ) + { + if( !rNewStr.isEmpty() ) + { + if( !rString.isEmpty() ) + rString += OUStringChar(cSeparator); + rString += rNewStr; + } + } + else + rString = rNewStr; +} + +sal_Int32 ScRangeStringConverter::IndexOf( + std::u16string_view rString, + sal_Unicode cSearchChar, + sal_Int32 nOffset, + sal_Unicode cQuote ) +{ + sal_Int32 nLength = rString.size(); + sal_Int32 nIndex = nOffset; + bool bQuoted = false; + bool bExitLoop = false; + + while( !bExitLoop && (nIndex >= 0 && nIndex < nLength) ) + { + sal_Unicode cCode = rString[ nIndex ]; + bExitLoop = (cCode == cSearchChar) && !bQuoted; + bQuoted = (bQuoted != (cCode == cQuote)); + if( !bExitLoop ) + nIndex++; + } + return (nIndex < nLength) ? nIndex : -1; +} + +sal_Int32 ScRangeStringConverter::IndexOfDifferent( + std::u16string_view rString, + sal_Unicode cSearchChar, + sal_Int32 nOffset ) +{ + sal_Int32 nLength = rString.size(); + sal_Int32 nIndex = nOffset; + bool bExitLoop = false; + + while( !bExitLoop && (nIndex >= 0 && nIndex < nLength) ) + { + bExitLoop = (rString[ nIndex ] != cSearchChar); + if( !bExitLoop ) + nIndex++; + } + return (nIndex < nLength) ? nIndex : -1; +} + +void ScRangeStringConverter::GetTokenByOffset( + OUString& rToken, + std::u16string_view rString, + sal_Int32& nOffset, + sal_Unicode cSeparator, + sal_Unicode cQuote) +{ + sal_Int32 nLength = rString.size(); + if( nOffset == -1 || nOffset >= nLength ) + { + rToken.clear(); + nOffset = -1; + } + else + { + sal_Int32 nTokenEnd = IndexOf( rString, cSeparator, nOffset, cQuote ); + if( nTokenEnd < 0 ) + nTokenEnd = nLength; + rToken = rString.substr( nOffset, nTokenEnd - nOffset ); + + sal_Int32 nNextBegin = IndexOfDifferent( rString, cSeparator, nTokenEnd ); + nOffset = (nNextBegin < 0) ? nLength : nNextBegin; + } +} + +void ScRangeStringConverter::AppendTableName(OUStringBuffer& rBuf, const OUString& rTabName) +{ + // quote character is always "'" + OUString aQuotedTab(rTabName); + ScCompiler::CheckTabQuotes(aQuotedTab); + rBuf.append(aQuotedTab); +} + +sal_Int32 ScRangeStringConverter::GetTokenCount( std::u16string_view rString, sal_Unicode cSeparator ) +{ + OUString sToken; + sal_Int32 nCount = 0; + sal_Int32 nOffset = 0; + while( nOffset >= 0 ) + { + GetTokenByOffset( sToken, rString, nOffset, '\'', cSeparator ); + if( nOffset >= 0 ) + nCount++; + } + return nCount; +} + +bool ScRangeStringConverter::GetAddressFromString( + ScAddress& rAddress, + std::u16string_view rAddressStr, + const ScDocument& rDocument, + FormulaGrammar::AddressConvention eConv, + sal_Int32& nOffset, + sal_Unicode cSeparator, + sal_Unicode cQuote ) +{ + OUString sToken; + GetTokenByOffset( sToken, rAddressStr, nOffset, cSeparator, cQuote ); + if( nOffset >= 0 ) + { + if ((rAddress.Parse( sToken, rDocument, eConv ) & ScRefFlags::VALID) == ScRefFlags::VALID) + return true; + ::formula::FormulaGrammar::AddressConvention eConvUI = rDocument.GetAddressConvention(); + if (eConv != eConvUI) + return ((rAddress.Parse(sToken, rDocument, eConvUI) & ScRefFlags::VALID) == ScRefFlags::VALID); + } + return false; +} + +bool ScRangeStringConverter::GetRangeFromString( + ScRange& rRange, + std::u16string_view rRangeStr, + const ScDocument& rDocument, + FormulaGrammar::AddressConvention eConv, + sal_Int32& nOffset, + sal_Unicode cSeparator, + sal_Unicode cQuote ) +{ + OUString sToken; + bool bResult(false); + GetTokenByOffset( sToken, rRangeStr, nOffset, cSeparator, cQuote ); + if( nOffset >= 0 ) + { + sal_Int32 nIndex = IndexOf( sToken, ':', 0, cQuote ); + OUString aUIString(sToken); + + if( nIndex < 0 ) + { + if ( aUIString[0] == '.' ) + aUIString = aUIString.copy( 1 ); + bResult = (rRange.aStart.Parse( aUIString, rDocument, eConv) & ScRefFlags::VALID) == + ScRefFlags::VALID; + ::formula::FormulaGrammar::AddressConvention eConvUI = rDocument.GetAddressConvention(); + if (!bResult && eConv != eConvUI) + bResult = (rRange.aStart.Parse(aUIString, rDocument, eConvUI) & ScRefFlags::VALID) == + ScRefFlags::VALID; + rRange.aEnd = rRange.aStart; + } + else + { + if ( aUIString[0] == '.' ) + { + aUIString = aUIString.copy( 1 ); + --nIndex; + } + + if ( nIndex < aUIString.getLength() - 1 && + aUIString[ nIndex + 1 ] == '.' ) + aUIString = aUIString.replaceAt( nIndex + 1, 1, u"" ); + + bResult = ((rRange.Parse(aUIString, rDocument, eConv) & ScRefFlags::VALID) == + ScRefFlags::VALID); + + // #i77703# chart ranges in the file format contain both sheet names, even for an external reference sheet. + // This isn't parsed by ScRange, so try to parse the two Addresses then. + if (!bResult) + { + bResult = ((rRange.aStart.Parse( aUIString.copy(0, nIndex), rDocument, eConv) + & ScRefFlags::VALID) == ScRefFlags::VALID) + && + ((rRange.aEnd.Parse( aUIString.copy(nIndex+1), rDocument, eConv) + & ScRefFlags::VALID) == ScRefFlags::VALID); + + ::formula::FormulaGrammar::AddressConvention eConvUI = rDocument.GetAddressConvention(); + if (!bResult && eConv != eConvUI) + { + bResult = ((rRange.aStart.Parse( aUIString.copy(0, nIndex), rDocument, eConvUI) + & ScRefFlags::VALID) == ScRefFlags::VALID) + && + ((rRange.aEnd.Parse( aUIString.copy(nIndex+1), rDocument, eConvUI) + & ScRefFlags::VALID) == ScRefFlags::VALID); + } + } + } + } + return bResult; +} + +bool ScRangeStringConverter::GetRangeListFromString( + ScRangeList& rRangeList, + std::u16string_view rRangeListStr, + const ScDocument& rDocument, + FormulaGrammar::AddressConvention eConv, + sal_Unicode cSeparator, + sal_Unicode cQuote ) +{ + bool bRet = true; + OSL_ENSURE( !rRangeListStr.empty(), "ScXMLConverter::GetRangeListFromString - empty string!" ); + sal_Int32 nOffset = 0; + while( nOffset >= 0 ) + { + ScRange aRange; + if ( + GetRangeFromString( aRange, rRangeListStr, rDocument, eConv, nOffset, cSeparator, cQuote ) && + (nOffset >= 0) + ) + { + rRangeList.push_back( aRange ); + } + else if (nOffset > -1) + bRet = false; + } + return bRet; +} + +bool ScRangeStringConverter::GetAreaFromString( + ScArea& rArea, + std::u16string_view rRangeStr, + const ScDocument& rDocument, + FormulaGrammar::AddressConvention eConv, + sal_Int32& nOffset, + sal_Unicode cSeparator ) +{ + ScRange aScRange; + bool bResult(false); + if( GetRangeFromString( aScRange, rRangeStr, rDocument, eConv, nOffset, cSeparator ) && (nOffset >= 0) ) + { + rArea.nTab = aScRange.aStart.Tab(); + rArea.nColStart = aScRange.aStart.Col(); + rArea.nRowStart = aScRange.aStart.Row(); + rArea.nColEnd = aScRange.aEnd.Col(); + rArea.nRowEnd = aScRange.aEnd.Row(); + bResult = true; + } + return bResult; +} + +bool ScRangeStringConverter::GetRangeFromString( + table::CellRangeAddress& rRange, + std::u16string_view rRangeStr, + const ScDocument& rDocument, + FormulaGrammar::AddressConvention eConv, + sal_Int32& nOffset, + sal_Unicode cSeparator ) +{ + ScRange aScRange; + bool bResult(false); + if( GetRangeFromString( aScRange, rRangeStr, rDocument, eConv, nOffset, cSeparator ) && (nOffset >= 0) ) + { + ScUnoConversion::FillApiRange( rRange, aScRange ); + bResult = true; + } + return bResult; +} + +void ScRangeStringConverter::GetStringFromAddress( + OUString& rString, + const ScAddress& rAddress, + const ScDocument* pDocument, + FormulaGrammar::AddressConvention eConv, + sal_Unicode cSeparator, + bool bAppendStr, + ScRefFlags nFormatFlags ) +{ + if (pDocument && pDocument->HasTable(rAddress.Tab())) + { + OUString sAddress(rAddress.Format(nFormatFlags, pDocument, eConv)); + AssignString( rString, sAddress, bAppendStr, cSeparator ); + } +} + +void ScRangeStringConverter::GetStringFromRange( + OUString& rString, + const ScRange& rRange, + const ScDocument* pDocument, + FormulaGrammar::AddressConvention eConv, + sal_Unicode cSeparator, + bool bAppendStr, + ScRefFlags nFormatFlags ) +{ + if (pDocument && pDocument->HasTable(rRange.aStart.Tab())) + { + ScAddress aStartAddress( rRange.aStart ); + ScAddress aEndAddress( rRange.aEnd ); + OUString sStartAddress(aStartAddress.Format(nFormatFlags, pDocument, eConv)); + OUString sEndAddress(aEndAddress.Format(nFormatFlags, pDocument, eConv)); + AssignString( + rString, sStartAddress + ":" + sEndAddress, bAppendStr, cSeparator); + } +} + +void ScRangeStringConverter::GetStringFromRangeList( + OUString& rString, + const ScRangeList* pRangeList, + const ScDocument* pDocument, + FormulaGrammar::AddressConvention eConv, + sal_Unicode cSeparator ) +{ + OUString sRangeListStr; + if( pRangeList ) + { + for( size_t nIndex = 0, nCount = pRangeList->size(); nIndex < nCount; nIndex++ ) + { + const ScRange & rRange = (*pRangeList)[nIndex]; + GetStringFromRange( sRangeListStr, rRange, pDocument, eConv, cSeparator, true ); + } + } + rString = sRangeListStr; +} + +void ScRangeStringConverter::GetStringFromArea( + OUString& rString, + const ScArea& rArea, + const ScDocument* pDocument, + FormulaGrammar::AddressConvention eConv, + sal_Unicode cSeparator, + bool bAppendStr, + ScRefFlags nFormatFlags ) +{ + ScRange aRange( rArea.nColStart, rArea.nRowStart, rArea.nTab, rArea.nColEnd, rArea.nRowEnd, rArea.nTab ); + GetStringFromRange( rString, aRange, pDocument, eConv, cSeparator, bAppendStr, nFormatFlags ); +} + +void ScRangeStringConverter::GetStringFromAddress( + OUString& rString, + const table::CellAddress& rAddress, + const ScDocument* pDocument, + FormulaGrammar::AddressConvention eConv, + sal_Unicode cSeparator, + bool bAppendStr ) +{ + ScAddress aScAddress( static_cast<SCCOL>(rAddress.Column), static_cast<SCROW>(rAddress.Row), rAddress.Sheet ); + GetStringFromAddress( rString, aScAddress, pDocument, eConv, cSeparator, bAppendStr ); +} + +void ScRangeStringConverter::GetStringFromRange( + OUString& rString, + const table::CellRangeAddress& rRange, + const ScDocument* pDocument, + FormulaGrammar::AddressConvention eConv, + sal_Unicode cSeparator, + bool bAppendStr, + ScRefFlags nFormatFlags ) +{ + ScRange aScRange( static_cast<SCCOL>(rRange.StartColumn), static_cast<SCROW>(rRange.StartRow), rRange.Sheet, + static_cast<SCCOL>(rRange.EndColumn), static_cast<SCROW>(rRange.EndRow), rRange.Sheet ); + GetStringFromRange( rString, aScRange, pDocument, eConv, cSeparator, bAppendStr, nFormatFlags ); +} + +void ScRangeStringConverter::GetStringFromRangeList( + OUString& rString, + const uno::Sequence< table::CellRangeAddress >& rRangeSeq, + const ScDocument* pDocument, + FormulaGrammar::AddressConvention eConv, + sal_Unicode cSeparator ) +{ + OUString sRangeListStr; + for( const table::CellRangeAddress& rRange : rRangeSeq ) + { + GetStringFromRange( sRangeListStr, rRange, pDocument, eConv, cSeparator, true ); + } + rString = sRangeListStr; +} + +static void lcl_appendCellAddress( + OUStringBuffer& rBuf, const ScDocument& rDoc, const ScAddress& rCell, + const ScAddress::ExternalInfo& rExtInfo) +{ + if (rExtInfo.mbExternal) + { + ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); + const OUString* pFilePath = pRefMgr->getExternalFileName(rExtInfo.mnFileId, true); + if (!pFilePath) + return; + + sal_Unicode cQuote = '\''; + rBuf.append(cQuote); + rBuf.append(*pFilePath); + rBuf.append(cQuote); + rBuf.append('#'); + rBuf.append('$'); + ScRangeStringConverter::AppendTableName(rBuf, rExtInfo.maTabName); + rBuf.append('.'); + + OUString aAddr(rCell.Format(ScRefFlags::ADDR_ABS, nullptr, rDoc.GetAddressConvention())); + rBuf.append(aAddr); + } + else + { + OUString aAddr(rCell.Format(ScRefFlags::ADDR_ABS_3D, &rDoc, rDoc.GetAddressConvention())); + rBuf.append(aAddr); + } +} + +static void lcl_appendCellRangeAddress( + OUStringBuffer& rBuf, const ScDocument& rDoc, const ScAddress& rCell1, const ScAddress& rCell2, + const ScAddress::ExternalInfo& rExtInfo1, const ScAddress::ExternalInfo& rExtInfo2) +{ + if (rExtInfo1.mbExternal) + { + OSL_ENSURE(rExtInfo2.mbExternal, "2nd address is not external!?"); + OSL_ENSURE(rExtInfo1.mnFileId == rExtInfo2.mnFileId, "File IDs do not match between 1st and 2nd addresses."); + + ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); + const OUString* pFilePath = pRefMgr->getExternalFileName(rExtInfo1.mnFileId, true); + if (!pFilePath) + return; + + sal_Unicode cQuote = '\''; + rBuf.append(cQuote); + rBuf.append(*pFilePath); + rBuf.append(cQuote); + rBuf.append('#'); + rBuf.append('$'); + ScRangeStringConverter::AppendTableName(rBuf, rExtInfo1.maTabName); + rBuf.append('.'); + + OUString aAddr(rCell1.Format(ScRefFlags::ADDR_ABS, nullptr, rDoc.GetAddressConvention())); + rBuf.append(aAddr); + + rBuf.append(":"); + + if (rExtInfo1.maTabName != rExtInfo2.maTabName) + { + rBuf.append('$'); + ScRangeStringConverter::AppendTableName(rBuf, rExtInfo2.maTabName); + rBuf.append('.'); + } + + aAddr = rCell2.Format(ScRefFlags::ADDR_ABS, nullptr, rDoc.GetAddressConvention()); + rBuf.append(aAddr); + } + else + { + ScRange aRange; + aRange.aStart = rCell1; + aRange.aEnd = rCell2; + OUString aAddr(aRange.Format(rDoc, ScRefFlags::RANGE_ABS_3D, rDoc.GetAddressConvention())); + rBuf.append(aAddr); + } +} + +void ScRangeStringConverter::GetStringFromXMLRangeString( OUString& rString, std::u16string_view rXMLRange, const ScDocument& rDoc ) +{ + FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention(); + const sal_Unicode cSepNew = ScCompiler::GetNativeSymbolChar(ocSep); + + OUStringBuffer aRetStr; + sal_Int32 nOffset = 0; + bool bFirst = true; + + while (nOffset >= 0) + { + OUString aToken; + GetTokenByOffset(aToken, rXMLRange, nOffset); + if (nOffset < 0) + break; + + sal_Int32 nSepPos = IndexOf(aToken, ':', 0); + if (nSepPos >= 0) + { + // Cell range + OUString aBeginCell = aToken.copy(0, nSepPos); + OUString aEndCell = aToken.copy(nSepPos+1); + + if (aBeginCell.isEmpty() || aEndCell.isEmpty()) + // both cell addresses must exist for this to work. + continue; + + sal_Int32 nEndCellDotPos = aEndCell.indexOf('.'); + if (nEndCellDotPos <= 0) + { + // initialize buffer with table name... + sal_Int32 nDotPos = IndexOf(aBeginCell, '.', 0); + OUStringBuffer aBuf(aBeginCell.subView(0, nDotPos)); + + if (nEndCellDotPos == 0) + { + // workaround for old syntax (probably pre-chart2 age?) + // e.g. Sheet1.A1:.B2 + aBuf.append(aEndCell); + } + else if (nEndCellDotPos < 0) + { + // sheet name in the end cell is omitted (e.g. Sheet2.A1:B2). + aBuf.append("." + aEndCell); + } + aEndCell = aBuf.makeStringAndClear(); + } + + ScAddress::ExternalInfo aExtInfo1, aExtInfo2; + ScAddress aCell1, aCell2; + ScRefFlags nRet = aCell1.Parse(aBeginCell, rDoc, FormulaGrammar::CONV_OOO, &aExtInfo1); + if ((nRet & ScRefFlags::VALID) == ScRefFlags::ZERO) + { + // first cell is invalid. + if (eConv == FormulaGrammar::CONV_OOO) + continue; + + nRet = aCell1.Parse(aBeginCell, rDoc, eConv, &aExtInfo1); + if ((nRet & ScRefFlags::VALID) == ScRefFlags::ZERO) + // first cell is really invalid. + continue; + } + + nRet = aCell2.Parse(aEndCell, rDoc, FormulaGrammar::CONV_OOO, &aExtInfo2); + if ((nRet & ScRefFlags::VALID) == ScRefFlags::ZERO) + { + // second cell is invalid. + if (eConv == FormulaGrammar::CONV_OOO) + continue; + + nRet = aCell2.Parse(aEndCell, rDoc, eConv, &aExtInfo2); + if ((nRet & ScRefFlags::VALID) == ScRefFlags::ZERO) + // second cell is really invalid. + continue; + } + + if (aExtInfo1.mnFileId != aExtInfo2.mnFileId || aExtInfo1.mbExternal != aExtInfo2.mbExternal) + // external info inconsistency. + continue; + + // All looks good! + + if (bFirst) + bFirst = false; + else + aRetStr.append(cSepNew); + + lcl_appendCellRangeAddress(aRetStr, rDoc, aCell1, aCell2, aExtInfo1, aExtInfo2); + } + else + { + // Chart always saves ranges using CONV_OOO convention. + ScAddress::ExternalInfo aExtInfo; + ScAddress aCell; + ScRefFlags nRet = aCell.Parse(aToken, rDoc, ::formula::FormulaGrammar::CONV_OOO, &aExtInfo); + if ((nRet & ScRefFlags::VALID) == ScRefFlags::ZERO ) + { + nRet = aCell.Parse(aToken, rDoc, eConv, &aExtInfo); + if ((nRet & ScRefFlags::VALID) == ScRefFlags::ZERO) + continue; + } + + // Looks good! + + if (bFirst) + bFirst = false; + else + aRetStr.append(cSepNew); + + lcl_appendCellAddress(aRetStr, rDoc, aCell, aExtInfo); + } + } + + rString = aRetStr.makeStringAndClear(); +} + +ScRangeData* ScRangeStringConverter::GetRangeDataFromString( const OUString& rString, const SCTAB nTab, + const ScDocument& rDoc, formula::FormulaGrammar::AddressConvention eConv ) +{ + // This may be called with an external 'doc'#name but wouldn't find any. + + // Dot '.' is not allowed in range names, if present only lookup if it's a + // sheet-local name. Same for '!' Excel syntax. + // If eConv == FormulaGrammar::CONV_A1_XL_A1 then try both, first our own. + sal_Int32 nIndex = -1; + if (eConv == FormulaGrammar::CONV_OOO || eConv == FormulaGrammar::CONV_A1_XL_A1) + nIndex = ScGlobal::FindUnquoted( rString, '.'); + if (nIndex < 0 && (eConv == FormulaGrammar::CONV_A1_XL_A1 + || eConv == FormulaGrammar::CONV_XL_A1 + || eConv == FormulaGrammar::CONV_XL_R1C1 + || eConv == FormulaGrammar::CONV_XL_OOX)) + nIndex = ScGlobal::FindUnquoted( rString, '!'); + + if (nIndex >= 0) + { + if (nIndex == 0) + return nullptr; // Can't be a name. + + OUString aTab( rString.copy( 0, nIndex)); + ScGlobal::EraseQuotes( aTab, '\''); + SCTAB nLocalTab; + if (!rDoc.GetTable( aTab, nLocalTab)) + return nullptr; + + ScRangeName* pLocalRangeName = rDoc.GetRangeName(nLocalTab); + if (!pLocalRangeName) + return nullptr; + + const OUString aName( rString.copy( nIndex+1)); + return pLocalRangeName->findByUpperName( ScGlobal::getCharClass().uppercase( aName)); + } + + ScRangeName* pLocalRangeName = rDoc.GetRangeName(nTab); + ScRangeData* pData = nullptr; + OUString aUpperName = ScGlobal::getCharClass().uppercase(rString); + if(pLocalRangeName) + { + pData = pLocalRangeName->findByUpperName(aUpperName); + } + if (!pData) + { + ScRangeName* pGlobalRangeName = rDoc.GetRangeName(); + if (pGlobalRangeName) + { + pData = pGlobalRangeName->findByUpperName(aUpperName); + } + } + return pData; +} + +ScArea::ScArea( SCTAB tab, + SCCOL colStart, SCROW rowStart, + SCCOL colEnd, SCROW rowEnd ) : + nTab ( tab ), + nColStart( colStart ), nRowStart( rowStart ), + nColEnd ( colEnd ), nRowEnd ( rowEnd ) +{ +} + +bool ScArea::operator==( const ScArea& r ) const +{ + return ( (nTab == r.nTab) + && (nColStart == r.nColStart) + && (nRowStart == r.nRowStart) + && (nColEnd == r.nColEnd) + && (nRowEnd == r.nRowEnd) ); +} + +ScAreaNameIterator::ScAreaNameIterator( const ScDocument& rDoc ) : + pRangeName(rDoc.GetRangeName()), + pDBCollection(rDoc.GetDBCollection()), + bFirstPass(true) +{ + if (pRangeName) + { + maRNPos = pRangeName->begin(); + maRNEnd = pRangeName->end(); + } +} + +bool ScAreaNameIterator::Next( OUString& rName, ScRange& rRange ) +{ + for (;;) + { + if ( bFirstPass ) // first the area names + { + if ( pRangeName && maRNPos != maRNEnd ) + { + const ScRangeData& rData = *maRNPos->second; + ++maRNPos; + bool bValid = rData.IsValidReference(rRange); + if (bValid) + { + rName = rData.GetName(); + return true; // found + } + } + else + { + bFirstPass = false; + if (pDBCollection) + { + const ScDBCollection::NamedDBs& rDBs = pDBCollection->getNamedDBs(); + maDBPos = rDBs.begin(); + maDBEnd = rDBs.end(); + } + } + } + + if ( !bFirstPass ) // then the DB areas + { + if (pDBCollection && maDBPos != maDBEnd) + { + const ScDBData& rData = **maDBPos; + ++maDBPos; + rData.GetArea(rRange); + rName = rData.GetName(); + return true; // found + } + else + return false; // nothing left + } + } +} + +void ScRangeUpdater::UpdateInsertTab(ScAddress& rAddr, const sc::RefUpdateInsertTabContext& rCxt) +{ + if (rCxt.mnInsertPos <= rAddr.Tab()) + { + rAddr.IncTab(rCxt.mnSheets); + } +} + +void ScRangeUpdater::UpdateDeleteTab(ScAddress& rAddr, const sc::RefUpdateDeleteTabContext& rCxt) +{ + if (rCxt.mnDeletePos <= rAddr.Tab()) + { + rAddr.SetTab( std::max<SCTAB>(0, rAddr.Tab() - rCxt.mnSheets)); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/rechead.cxx b/sc/source/core/tool/rechead.cxx new file mode 100644 index 0000000000..abd44d0419 --- /dev/null +++ b/sc/source/core/tool/rechead.cxx @@ -0,0 +1,148 @@ +/* -*- 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 <rechead.hxx> +#include <scerrors.hxx> + +#include <osl/diagnose.h> + +ScMultipleReadHeader::ScMultipleReadHeader(SvStream& rNewStream) : + rStream( rNewStream ) +{ + sal_uInt32 nDataSize; + rStream.ReadUInt32( nDataSize ); + sal_uInt64 nDataPos = rStream.Tell(); + nTotalEnd = nDataPos + nDataSize; + nEntryEnd = nTotalEnd; + + rStream.SeekRel(nDataSize); + sal_uInt16 nID; + rStream.ReadUInt16( nID ); + if (nID != SCID_SIZES) + { + OSL_FAIL("SCID_SIZES not found"); + if ( rStream.GetError() == ERRCODE_NONE ) + rStream.SetError( SVSTREAM_FILEFORMAT_ERROR ); + + // everything to 0, so BytesLeft() aborts at least + pBuf = nullptr; pMemStream.reset(); + nEntryEnd = nDataPos; + } + else + { + sal_uInt32 nSizeTableLen; + rStream.ReadUInt32( nSizeTableLen ); + pBuf.reset( new sal_uInt8[nSizeTableLen] ); + rStream.ReadBytes( pBuf.get(), nSizeTableLen ); + pMemStream.reset(new SvMemoryStream( pBuf.get(), nSizeTableLen, StreamMode::READ )); + } + + nEndPos = rStream.Tell(); + rStream.Seek( nDataPos ); +} + +ScMultipleReadHeader::~ScMultipleReadHeader() +{ + if ( pMemStream && pMemStream->Tell() != pMemStream->GetEndOfData() ) + { + OSL_FAIL( "Sizes not fully read" ); + if ( rStream.GetError() == ERRCODE_NONE ) + rStream.SetError( SCWARN_IMPORT_INFOLOST ); + } + pMemStream.reset(); + + rStream.Seek(nEndPos); +} + +void ScMultipleReadHeader::EndEntry() +{ + sal_uInt64 nPos = rStream.Tell(); + OSL_ENSURE( nPos <= nEntryEnd, "read too much" ); + if ( nPos != nEntryEnd ) + { + if ( rStream.GetError() == ERRCODE_NONE ) + rStream.SetError( SCWARN_IMPORT_INFOLOST ); + rStream.Seek( nEntryEnd ); // ignore the rest + } + + nEntryEnd = nTotalEnd; // all remaining, if no StartEntry follows +} + +void ScMultipleReadHeader::StartEntry() +{ + sal_uInt64 nPos = rStream.Tell(); + sal_uInt32 nEntrySize; + (*pMemStream).ReadUInt32( nEntrySize ); + + nEntryEnd = nPos + nEntrySize; + OSL_ENSURE( nEntryEnd <= nTotalEnd, "read too many entries" ); +} + +sal_uInt64 ScMultipleReadHeader::BytesLeft() const +{ + sal_uInt64 nReadEnd = rStream.Tell(); + if (nReadEnd <= nEntryEnd) + return nEntryEnd-nReadEnd; + + OSL_FAIL("ScMultipleReadHeader::BytesLeft: Error"); + return 0; +} + +ScMultipleWriteHeader::ScMultipleWriteHeader(SvStream& rNewStream) : + rStream( rNewStream ), + aMemStream( 4096, 4096 ) +{ + nDataSize = 0; + rStream.WriteUInt32( nDataSize ); + + nDataPos = rStream.Tell(); + nEntryStart = nDataPos; +} + +ScMultipleWriteHeader::~ScMultipleWriteHeader() +{ + sal_uInt64 nDataEnd = rStream.Tell(); + + rStream.WriteUInt16( SCID_SIZES ); + rStream.WriteUInt32( aMemStream.Tell() ); + rStream.WriteBytes( aMemStream.GetData(), aMemStream.Tell() ); + + if ( nDataEnd - nDataPos != nDataSize ) // matched default ? + { + nDataSize = nDataEnd - nDataPos; + sal_uInt64 nPos = rStream.Tell(); + rStream.Seek(nDataPos-sizeof(sal_uInt32)); + rStream.WriteUInt32( nDataSize ); // record size at the beginning + rStream.Seek(nPos); + } +} + +void ScMultipleWriteHeader::EndEntry() +{ + sal_uInt64 nPos = rStream.Tell(); + aMemStream.WriteUInt32( nPos - nEntryStart ); +} + +void ScMultipleWriteHeader::StartEntry() +{ + sal_uInt64 nPos = rStream.Tell(); + nEntryStart = nPos; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/recursionhelper.cxx b/sc/source/core/tool/recursionhelper.cxx new file mode 100644 index 0000000000..59601f37a0 --- /dev/null +++ b/sc/source/core/tool/recursionhelper.cxx @@ -0,0 +1,304 @@ +/* -*- 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/. + */ + +#include <recursionhelper.hxx> +#include <formulacell.hxx> + +void ScRecursionHelper::Init() +{ + nRecursionCount = 0; + nDependencyComputationLevel = 0; + bInRecursionReturn = bDoingRecursion = bInIterationReturn = false; + bAbortingDependencyComputation = false; + aInsertPos = GetIterationEnd(); + ResetIteration(); + // Must not force clear aFGList ever. +} + +void ScRecursionHelper::ResetIteration() +{ + aLastIterationStart = GetIterationEnd(); + nIteration = 0; + bConverging = false; +} + +ScRecursionHelper::ScRecursionHelper() +{ + pFGSet = nullptr; + bGroupsIndependent = true; + Init(); +} + +void ScRecursionHelper::SetInRecursionReturn( bool b ) +{ + // Do not use IsInRecursionReturn() here, it decouples iteration. + if (b && !bInRecursionReturn) + aInsertPos = aRecursionFormulas.begin(); + bInRecursionReturn = b; +} + +void ScRecursionHelper::Insert( + ScFormulaCell* p, bool bOldRunning, const ScFormulaResult & rRes ) +{ + aRecursionFormulas.insert( aInsertPos, ScFormulaRecursionEntry( p, + bOldRunning, rRes)); +} + +void ScRecursionHelper::SetInIterationReturn( bool b ) +{ + // An iteration return is always coupled to a recursion return. + SetInRecursionReturn( b); + bInIterationReturn = b; +} + +void ScRecursionHelper::StartIteration() +{ + SetInIterationReturn( false); + nIteration = 1; + bConverging = false; + aLastIterationStart = GetIterationStart(); +} + +void ScRecursionHelper::ResumeIteration() +{ + SetInIterationReturn( false); + aLastIterationStart = GetIterationStart(); +} + +void ScRecursionHelper::IncIteration() +{ + ++nIteration; +} + +void ScRecursionHelper::EndIteration() +{ + aRecursionFormulas.erase( GetIterationStart(), GetIterationEnd()); + ResetIteration(); +} + +ScFormulaRecursionList::iterator ScRecursionHelper::GetIterationStart() +{ + return aRecursionFormulas.begin(); +} + +ScFormulaRecursionList::iterator ScRecursionHelper::GetIterationEnd() +{ + return aRecursionFormulas.end(); +} + +void ScRecursionHelper::Clear() +{ + aRecursionFormulas.clear(); + while (!aRecursionInIterationStack.empty()) + aRecursionInIterationStack.pop(); + Init(); +} + +static ScFormulaCell* lcl_GetTopCell(ScFormulaCell* pCell) +{ + if (!pCell) + return nullptr; + + const ScFormulaCellGroupRef& mxGroup = pCell->GetCellGroup(); + if (!mxGroup) + return pCell; + return mxGroup->mpTopCell; +} + +bool ScRecursionHelper::PushFormulaGroup(ScFormulaCell* pCell) +{ + assert(pCell); + + if (pCell->GetSeenInPath()) + { + // Found a simple cycle of formula-groups. + // Disable group calc for all elements of this cycle. + sal_Int32 nIdx = aFGList.size(); + assert(nIdx > 0); + do + { + --nIdx; + assert(nIdx >= 0); + const ScFormulaCellGroupRef& mxGroup = aFGList[nIdx]->GetCellGroup(); + if (mxGroup) + mxGroup->mbPartOfCycle = true; + } while (aFGList[nIdx] != pCell); + + return false; + } + + pCell->SetSeenInPath(true); + aFGList.push_back(pCell); + aInDependencyEvalMode.push_back(false); + return true; +} + +void ScRecursionHelper::PopFormulaGroup() +{ + assert(aFGList.size() == aInDependencyEvalMode.size()); + if (aFGList.empty()) + return; + ScFormulaCell* pCell = aFGList.back(); + pCell->SetSeenInPath(false); + aFGList.pop_back(); + aInDependencyEvalMode.pop_back(); +} + +bool ScRecursionHelper::AnyCycleMemberInDependencyEvalMode(const ScFormulaCell* pCell) +{ + assert(pCell); + + if (pCell->GetSeenInPath()) + { + // Found a simple cycle of formula-groups. + sal_Int32 nIdx = aFGList.size(); + assert(nIdx > 0); + do + { + --nIdx; + assert(nIdx >= 0); + const ScFormulaCellGroupRef& mxGroup = aFGList[nIdx]->GetCellGroup(); + // Found a cycle member FG that is in dependency evaluation mode. + if (mxGroup && aInDependencyEvalMode[nIdx]) + return true; + } while (aFGList[nIdx] != pCell); + + return false; + } + + return false; +} + +bool ScRecursionHelper::AnyParentFGInCycle() +{ + sal_Int32 nIdx = aFGList.size() - 1; + while (nIdx >= 0) + { + const ScFormulaCellGroupRef& mxGroup = aFGList[nIdx]->GetCellGroup(); + if (mxGroup) + return mxGroup->mbPartOfCycle; + --nIdx; + }; + return false; +} + +void ScRecursionHelper::SetFormulaGroupDepEvalMode(bool bSet) +{ + assert(aFGList.size()); + assert(aFGList.size() == aInDependencyEvalMode.size()); + assert(aFGList.back()->GetCellGroup()); + aInDependencyEvalMode.back() = bSet; +} + +void ScRecursionHelper::AbortDependencyComputation() +{ + assert( nDependencyComputationLevel > 0 ); + bAbortingDependencyComputation = true; +} + +void ScRecursionHelper::IncDepComputeLevel() +{ + ++nDependencyComputationLevel; +} + +void ScRecursionHelper::DecDepComputeLevel() +{ + --nDependencyComputationLevel; + bAbortingDependencyComputation = false; +} + +void ScRecursionHelper::AddTemporaryGroupCell(ScFormulaCell* cell) +{ + aTemporaryGroupCells.push_back( cell ); +} + +void ScRecursionHelper::CleanTemporaryGroupCells() +{ + if( GetRecursionCount() == 0 ) + { + for( ScFormulaCell* cell : aTemporaryGroupCells ) + cell->SetCellGroup( nullptr ); + aTemporaryGroupCells.clear(); + } +} + +bool ScRecursionHelper::CheckFGIndependence(ScFormulaCellGroup* pFG) +{ + if (pFGSet && pFGSet->count(pFG)) + { + bGroupsIndependent = false; + return false; + } + + return true; +} + +ScFormulaGroupCycleCheckGuard::ScFormulaGroupCycleCheckGuard(ScRecursionHelper& rRecursionHelper, ScFormulaCell* pCell) : + mrRecHelper(rRecursionHelper) +{ + if (pCell) + { + pCell = lcl_GetTopCell(pCell); + mbShouldPop = mrRecHelper.PushFormulaGroup(pCell); + } + else + mbShouldPop = false; +} + +ScFormulaGroupCycleCheckGuard::~ScFormulaGroupCycleCheckGuard() +{ + if (mbShouldPop) + mrRecHelper.PopFormulaGroup(); +} + +ScFormulaGroupDependencyComputeGuard::ScFormulaGroupDependencyComputeGuard(ScRecursionHelper& rRecursionHelper) : + mrRecHelper(rRecursionHelper) +{ + mrRecHelper.IncDepComputeLevel(); + mrRecHelper.SetFormulaGroupDepEvalMode(true); +} + +ScFormulaGroupDependencyComputeGuard::~ScFormulaGroupDependencyComputeGuard() +{ + mrRecHelper.SetFormulaGroupDepEvalMode(false); + mrRecHelper.DecDepComputeLevel(); +} + +ScCheckIndependentFGGuard::ScCheckIndependentFGGuard(ScRecursionHelper& rRecursionHelper, + o3tl::sorted_vector<ScFormulaCellGroup*>* pSet) : + mrRecHelper(rRecursionHelper), + mbUsedFGSet(false) +{ + if (!mrRecHelper.HasFormulaGroupSet()) + { + mrRecHelper.SetFormulaGroupSet(pSet); + mrRecHelper.SetGroupsIndependent(true); + mbUsedFGSet = true; + } +} + +ScCheckIndependentFGGuard::~ScCheckIndependentFGGuard() +{ + if (mbUsedFGSet) + { + // Reset to defaults. + mrRecHelper.SetFormulaGroupSet(nullptr); + mrRecHelper.SetGroupsIndependent(true); + } +} + +bool ScCheckIndependentFGGuard::AreGroupsIndependent() +{ + if (!mbUsedFGSet) + return false; + + return mrRecHelper.AreGroupsIndependent(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/refdata.cxx b/sc/source/core/tool/refdata.cxx new file mode 100644 index 0000000000..3e1b9b1af8 --- /dev/null +++ b/sc/source/core/tool/refdata.cxx @@ -0,0 +1,599 @@ +/* -*- 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 <sal/config.h> + +#include <algorithm> + +#include <refdata.hxx> +#include <document.hxx> + +void ScSingleRefData::InitAddress( const ScAddress& rAdr ) +{ + InitAddress( rAdr.Col(), rAdr.Row(), rAdr.Tab()); +} + +void ScSingleRefData::InitAddress( SCCOL nColP, SCROW nRowP, SCTAB nTabP ) +{ + InitFlags(); + mnCol = nColP; + mnRow = nRowP; + mnTab = nTabP; +} + +void ScSingleRefData::InitAddressRel( const ScDocument& rDoc, const ScAddress& rAdr, const ScAddress& rPos ) +{ + InitFlags(); + SetColRel(true); + SetRowRel(true); + SetTabRel(true); + SetAddress(rDoc.GetSheetLimits(), rAdr, rPos); +} + +void ScSingleRefData::InitFromRefAddress( const ScDocument& rDoc, const ScRefAddress& rRef, const ScAddress& rPos ) +{ + InitFlags(); + SetColRel( rRef.IsRelCol()); + SetRowRel( rRef.IsRelRow()); + SetTabRel( rRef.IsRelTab()); + SetFlag3D( rRef.Tab() != rPos.Tab()); + SetAddress( rDoc.GetSheetLimits(), rRef.GetAddress(), rPos); +} + +void ScSingleRefData::SetAbsCol( SCCOL nVal ) +{ + Flags.bColRel = false; + mnCol = nVal; +} + +void ScSingleRefData::SetRelCol( SCCOL nVal ) +{ + Flags.bColRel = true; + mnCol = nVal; +} + +void ScSingleRefData::IncCol( SCCOL nInc ) +{ + mnCol += nInc; +} + +void ScSingleRefData::SetAbsRow( SCROW nVal ) +{ + Flags.bRowRel = false; + mnRow = nVal; +} + +void ScSingleRefData::SetRelRow( SCROW nVal ) +{ + Flags.bRowRel = true; + mnRow = nVal; +} + +void ScSingleRefData::IncRow( SCROW nInc ) +{ + mnRow += nInc; +} + +void ScSingleRefData::SetAbsTab( SCTAB nVal ) +{ + Flags.bTabRel = false; + mnTab = nVal; +} + +void ScSingleRefData::SetRelTab( SCTAB nVal ) +{ + Flags.bTabRel = true; + mnTab = nVal; +} + +void ScSingleRefData::IncTab( SCTAB nInc ) +{ + mnTab += nInc; +} + +void ScSingleRefData::SetColDeleted( bool bVal ) +{ + Flags.bColDeleted = bVal; +} + +void ScSingleRefData::SetRowDeleted( bool bVal ) +{ + Flags.bRowDeleted = bVal; +} + +void ScSingleRefData::SetTabDeleted( bool bVal ) +{ + Flags.bTabDeleted = bVal; +} + +bool ScSingleRefData::IsDeleted() const +{ + return IsColDeleted() || IsRowDeleted() || IsTabDeleted(); +} + +bool ScSingleRefData::Valid(const ScDocument& rDoc) const +{ + return !IsDeleted() && ColValid(rDoc) && RowValid(rDoc) && TabValid(rDoc); +} + +bool ScSingleRefData::ColValid(const ScDocument& rDoc) const +{ + if (Flags.bColRel) + { + if (mnCol < -rDoc.MaxCol() || rDoc.MaxCol() < mnCol) + return false; + } + else + { + if (mnCol < 0 || rDoc.MaxCol() < mnCol) + return false; + } + + return true; +} + +bool ScSingleRefData::RowValid(const ScDocument& rDoc) const +{ + if (Flags.bRowRel) + { + if (mnRow < -rDoc.MaxRow() || rDoc.MaxRow() < mnRow) + return false; + } + else + { + if (mnRow < 0 || rDoc.MaxRow() < mnRow) + return false; + } + + return true; +} + +bool ScSingleRefData::TabValid(const ScDocument& rDoc) const +{ + if (Flags.bTabRel) + { + if (mnTab < -MAXTAB || MAXTAB < mnTab) + return false; + } + else + { + if (mnTab < 0 || rDoc.GetTableCount() <= mnTab) + return false; + } + + return true; +} + +bool ScSingleRefData::ValidExternal(const ScDocument& rDoc) const +{ + return ColValid(rDoc) && RowValid(rDoc) && mnTab >= -1; +} + +ScAddress ScSingleRefData::toAbs( const ScDocument& rDoc, const ScAddress& rPos ) const +{ + return toAbs(rDoc.GetSheetLimits(), rPos); +} + +ScAddress ScSingleRefData::toAbs( const ScSheetLimits& rLimits, const ScAddress& rPos ) const +{ + SCCOL nRetCol = Flags.bColRel ? mnCol + rPos.Col() : mnCol; + SCROW nRetRow = Flags.bRowRel ? mnRow + rPos.Row() : mnRow; + SCTAB nRetTab = Flags.bTabRel ? mnTab + rPos.Tab() : mnTab; + + ScAddress aAbs(ScAddress::INITIALIZE_INVALID); + + if (rLimits.ValidCol(nRetCol)) + aAbs.SetCol(nRetCol); + + if (rLimits.ValidRow(nRetRow)) + aAbs.SetRow(nRetRow); + + if (ValidTab(nRetTab)) + aAbs.SetTab(nRetTab); + + return aAbs; +} + +void ScSingleRefData::SetAddress( const ScSheetLimits& rLimits, const ScAddress& rAddr, const ScAddress& rPos ) +{ + if (Flags.bColRel) + mnCol = rAddr.Col() - rPos.Col(); + else + mnCol = rAddr.Col(); + + if (!rLimits.ValidCol(rAddr.Col())) + SetColDeleted(true); + + if (Flags.bRowRel) + mnRow = rAddr.Row() - rPos.Row(); + else + mnRow = rAddr.Row(); + + if (!rLimits.ValidRow(rAddr.Row())) + SetRowDeleted(true); + + if (Flags.bTabRel) + mnTab = rAddr.Tab() - rPos.Tab(); + else + mnTab = rAddr.Tab(); + + if (!ValidTab( rAddr.Tab(), MAXTAB)) + SetTabDeleted(true); +} + +SCROW ScSingleRefData::Row() const +{ + if (Flags.bRowDeleted) + return -1; + return mnRow; +} + +SCCOL ScSingleRefData::Col() const +{ + if (Flags.bColDeleted) + return -1; + return mnCol; +} + +SCTAB ScSingleRefData::Tab() const +{ + if (Flags.bTabDeleted) + return -1; + return mnTab; +} + +// static +void ScSingleRefData::PutInOrder( ScSingleRefData& rRef1, ScSingleRefData& rRef2, const ScAddress& rPos ) +{ + const sal_uInt8 kCOL = 1; + const sal_uInt8 kROW = 2; + const sal_uInt8 kTAB = 4; + + sal_uInt8 nRelState1 = rRef1.Flags.bRelName ? + ((rRef1.Flags.bTabRel ? kTAB : 0) | + (rRef1.Flags.bRowRel ? kROW : 0) | + (rRef1.Flags.bColRel ? kCOL : 0)) : + 0; + + sal_uInt8 nRelState2 = rRef2.Flags.bRelName ? + ((rRef2.Flags.bTabRel ? kTAB : 0) | + (rRef2.Flags.bRowRel ? kROW : 0) | + (rRef2.Flags.bColRel ? kCOL : 0)) : + 0; + + SCCOL nCol1 = rRef1.Flags.bColRel ? rPos.Col() + rRef1.mnCol : rRef1.mnCol; + SCCOL nCol2 = rRef2.Flags.bColRel ? rPos.Col() + rRef2.mnCol : rRef2.mnCol; + if (nCol2 < nCol1) + { + rRef1.mnCol = rRef2.Flags.bColRel ? nCol2 - rPos.Col() : nCol2; + rRef2.mnCol = rRef1.Flags.bColRel ? nCol1 - rPos.Col() : nCol1; + if (rRef1.Flags.bRelName && rRef1.Flags.bColRel) + nRelState2 |= kCOL; + else + nRelState2 &= ~kCOL; + if (rRef2.Flags.bRelName && rRef2.Flags.bColRel) + nRelState1 |= kCOL; + else + nRelState1 &= ~kCOL; + bool bTmp = rRef1.Flags.bColRel; + rRef1.Flags.bColRel = rRef2.Flags.bColRel; + rRef2.Flags.bColRel = bTmp; + bTmp = rRef1.Flags.bColDeleted; + rRef1.Flags.bColDeleted = rRef2.Flags.bColDeleted; + rRef2.Flags.bColDeleted = bTmp; + } + + SCROW nRow1 = rRef1.Flags.bRowRel ? rPos.Row() + rRef1.mnRow : rRef1.mnRow; + SCROW nRow2 = rRef2.Flags.bRowRel ? rPos.Row() + rRef2.mnRow : rRef2.mnRow; + if (nRow2 < nRow1) + { + rRef1.mnRow = rRef2.Flags.bRowRel ? nRow2 - rPos.Row() : nRow2; + rRef2.mnRow = rRef1.Flags.bRowRel ? nRow1 - rPos.Row() : nRow1; + if (rRef1.Flags.bRelName && rRef1.Flags.bRowRel) + nRelState2 |= kROW; + else + nRelState2 &= ~kROW; + if (rRef2.Flags.bRelName && rRef2.Flags.bRowRel) + nRelState1 |= kROW; + else + nRelState1 &= ~kROW; + bool bTmp = rRef1.Flags.bRowRel; + rRef1.Flags.bRowRel = rRef2.Flags.bRowRel; + rRef2.Flags.bRowRel = bTmp; + bTmp = rRef1.Flags.bRowDeleted; + rRef1.Flags.bRowDeleted = rRef2.Flags.bRowDeleted; + rRef2.Flags.bRowDeleted = bTmp; + } + + SCTAB nTab1 = rRef1.Flags.bTabRel ? rPos.Tab() + rRef1.mnTab : rRef1.mnTab; + SCTAB nTab2 = rRef2.Flags.bTabRel ? rPos.Tab() + rRef2.mnTab : rRef2.mnTab; + if (nTab2 < nTab1) + { + rRef1.mnTab = rRef2.Flags.bTabRel ? nTab2 - rPos.Tab() : nTab2; + rRef2.mnTab = rRef1.Flags.bTabRel ? nTab1 - rPos.Tab() : nTab1; + if (rRef1.Flags.bRelName && rRef1.Flags.bTabRel) + nRelState2 |= kTAB; + else + nRelState2 &= ~kTAB; + if (rRef2.Flags.bRelName && rRef2.Flags.bTabRel) + nRelState1 |= kTAB; + else + nRelState1 &= ~kTAB; + bool bTmp = rRef1.Flags.bTabRel; + rRef1.Flags.bTabRel = rRef2.Flags.bTabRel; + rRef2.Flags.bTabRel = bTmp; + bTmp = rRef1.Flags.bTabDeleted; + rRef1.Flags.bTabDeleted = rRef2.Flags.bTabDeleted; + rRef2.Flags.bTabDeleted = bTmp; + } + + // bFlag3D stays the same on both references. + + rRef1.Flags.bRelName = (nRelState1 != 0); + rRef2.Flags.bRelName = (nRelState2 != 0); +} + +bool ScSingleRefData::operator==( const ScSingleRefData& r ) const +{ + return mnFlagValue == r.mnFlagValue && mnCol == r.mnCol && mnRow == r.mnRow && mnTab == r.mnTab; +} + +bool ScSingleRefData::operator!=( const ScSingleRefData& r ) const +{ + return !operator==(r); +} + +#if DEBUG_FORMULA_COMPILER +void ScSingleRefData::Dump( int nIndent ) const +{ + std::string aIndent; + for (int i = 0; i < nIndent; ++i) + aIndent += " "; + + cout << aIndent << "address type column: " << (IsColRel()?"relative":"absolute") + << " row : " << (IsRowRel()?"relative":"absolute") << " sheet: " + << (IsTabRel()?"relative":"absolute") << endl; + cout << aIndent << "deleted column: " << (IsColDeleted()?"yes":"no") + << " row : " << (IsRowDeleted()?"yes":"no") << " sheet: " + << (IsTabDeleted()?"yes":"no") << endl; + cout << aIndent << "column: " << mnCol << " row: " << mnRow << " sheet: " << mnTab << endl; + cout << aIndent << "3d ref: " << (IsFlag3D()?"yes":"no") << endl; +} +#endif + +void ScComplexRefData::InitFromRefAddresses( const ScDocument& rDoc, const ScRefAddress& rRef1, const ScRefAddress& rRef2, const ScAddress& rPos ) +{ + InitFlags(); + Ref1.SetColRel( rRef1.IsRelCol()); + Ref1.SetRowRel( rRef1.IsRelRow()); + Ref1.SetTabRel( rRef1.IsRelTab()); + Ref1.SetFlag3D( rRef1.Tab() != rPos.Tab() || rRef1.Tab() != rRef2.Tab()); + Ref2.SetColRel( rRef2.IsRelCol()); + Ref2.SetRowRel( rRef2.IsRelRow()); + Ref2.SetTabRel( rRef2.IsRelTab()); + Ref2.SetFlag3D( rRef1.Tab() != rRef2.Tab()); + SetRange( rDoc.GetSheetLimits(), ScRange( rRef1.GetAddress(), rRef2.GetAddress()), rPos); +} + +ScComplexRefData& ScComplexRefData::Extend( const ScSheetLimits& rLimits, const ScSingleRefData & rRef, const ScAddress & rPos ) +{ + bool bInherit3D = (Ref1.IsFlag3D() && !Ref2.IsFlag3D() && !rRef.IsFlag3D()); + ScRange aAbsRange = toAbs(rLimits, rPos); + + ScSingleRefData aRef = rRef; + // If no sheet was given in the extending part, let it point to the same + // sheet as this reference's end point, inheriting the absolute/relative + // mode. + // [$]Sheet1.A5:A6:A7 on Sheet2 do still reference only Sheet1. + if (!rRef.IsFlag3D()) + { + if (Ref2.IsTabRel()) + aRef.SetRelTab( Ref2.Tab()); + else + aRef.SetAbsTab( Ref2.Tab()); + } + ScAddress aAbs = aRef.toAbs(rLimits, rPos); + + if (aAbs.Col() < aAbsRange.aStart.Col()) + aAbsRange.aStart.SetCol(aAbs.Col()); + + if (aAbs.Row() < aAbsRange.aStart.Row()) + aAbsRange.aStart.SetRow(aAbs.Row()); + + if (aAbs.Tab() < aAbsRange.aStart.Tab()) + aAbsRange.aStart.SetTab(aAbs.Tab()); + + if (aAbsRange.aEnd.Col() < aAbs.Col()) + aAbsRange.aEnd.SetCol(aAbs.Col()); + + if (aAbsRange.aEnd.Row() < aAbs.Row()) + aAbsRange.aEnd.SetRow(aAbs.Row()); + + if (aAbsRange.aEnd.Tab() < aAbs.Tab()) + aAbsRange.aEnd.SetTab(aAbs.Tab()); + + // In Ref2 inherit absolute/relative addressing from the extending part. + // A$5:A5 => A$5:A$5:A5 => A$5:A5, and not A$5:A$5 + // A$6:$A5 => A$6:A$6:$A5 => A5:$A$6 + if (aAbsRange.aEnd.Col() == aAbs.Col()) + Ref2.SetColRel( rRef.IsColRel()); + if (aAbsRange.aEnd.Row() == aAbs.Row()) + Ref2.SetRowRel( rRef.IsRowRel()); + + // In Ref1 inherit relative sheet from extending part if given. + if (aAbsRange.aStart.Tab() == aAbs.Tab() && rRef.IsFlag3D()) + Ref1.SetTabRel( rRef.IsTabRel()); + + // In Ref2 inherit relative sheet from either Ref1 or extending part. + // Use the original 3D flags to determine which. + // $Sheet1.$A$5:$A$6 => $Sheet1.$A$5:$A$5:$A$6 => $Sheet1.$A$5:$A$6, and + // not $Sheet1.$A$5:Sheet1.$A$6 (with invisible second 3D, but relative). + if (aAbsRange.aEnd.Tab() == aAbs.Tab()) + Ref2.SetTabRel( bInherit3D ? Ref1.IsTabRel() : rRef.IsTabRel()); + + // Force 3D flag in Ref1 if different sheet or more than one sheet + // referenced. + if (aAbsRange.aStart.Tab() != rPos.Tab() || aAbsRange.aStart.Tab() != aAbsRange.aEnd.Tab()) + Ref1.SetFlag3D(true); + + // Force 3D flag in Ref2 if more than one sheet referenced. + if (aAbsRange.aStart.Tab() != aAbsRange.aEnd.Tab()) + Ref2.SetFlag3D(true); + + // Inherit 3D flag in Ref1 from extending part in case range wasn't + // extended as in A5:A5:Sheet1.A5 if on Sheet1. + if (rRef.IsFlag3D()) + Ref1.SetFlag3D( true); + + // Inherit RelNameRef from extending part. + if (rRef.IsRelName()) + Ref2.SetRelName(true); + + SetRange(rLimits, aAbsRange, rPos); + + return *this; +} + +ScComplexRefData& ScComplexRefData::Extend( const ScSheetLimits& rLimits, const ScComplexRefData & rRef, const ScAddress & rPos ) +{ + return Extend( rLimits, rRef.Ref1, rPos).Extend( rLimits, rRef.Ref2, rPos); +} + +bool ScComplexRefData::Valid(const ScDocument& rDoc) const +{ + return Ref1.Valid(rDoc) && Ref2.Valid(rDoc); +} + +bool ScComplexRefData::ValidExternal(const ScDocument& rDoc) const +{ + return Ref1.ValidExternal(rDoc) && Ref2.ColValid(rDoc) && Ref2.RowValid(rDoc) && Ref1.Tab() <= Ref2.Tab(); +} + +ScRange ScComplexRefData::toAbs( const ScDocument& rDoc, const ScAddress& rPos ) const +{ + return toAbs(rDoc.GetSheetLimits(), rPos); +} + +ScRange ScComplexRefData::toAbs( const ScSheetLimits& rLimits, const ScAddress& rPos ) const +{ + return ScRange(Ref1.toAbs(rLimits, rPos), Ref2.toAbs(rLimits, rPos)); +} + +void ScComplexRefData::SetRange( const ScSheetLimits& rLimits, const ScRange& rRange, const ScAddress& rPos ) +{ + Ref1.SetAddress(rLimits, rRange.aStart, rPos); + Ref2.SetAddress(rLimits, rRange.aEnd, rPos); +} + +void ScComplexRefData::PutInOrder( const ScAddress& rPos ) +{ + ScSingleRefData::PutInOrder( Ref1, Ref2, rPos); +} + +bool ScComplexRefData::IsEntireCol( const ScSheetLimits& rLimits ) const +{ + // Both row anchors must be absolute. + return Ref1.Row() == 0 && Ref2.Row() == rLimits.MaxRow() && !Ref1.IsRowRel() && !Ref2.IsRowRel(); +} + +/** Whether this references entire rows, 1:1 */ +bool ScComplexRefData::IsEntireRow( const ScSheetLimits& rLimits ) const +{ + // Both column anchors must be absolute. + return Ref1.Col() == 0 && Ref2.Col() == rLimits.MaxCol() && !Ref1.IsColRel() && !Ref2.IsColRel(); +} + +bool ScComplexRefData::IncEndColSticky( const ScDocument& rDoc, SCCOL nDelta, const ScAddress& rPos ) +{ + SCCOL nCol1 = Ref1.IsColRel() ? Ref1.Col() + rPos.Col() : Ref1.Col(); + SCCOL nCol2 = Ref2.IsColRel() ? Ref2.Col() + rPos.Col() : Ref2.Col(); + if (nCol1 >= nCol2) + { + // Less than two columns => not sticky. + Ref2.IncCol( nDelta); + return true; + } + + if (nCol2 == rDoc.MaxCol()) + // already sticky + return false; + + if (nCol2 < rDoc.MaxCol()) + { + SCCOL nCol = ::std::min( static_cast<SCCOL>(nCol2 + nDelta), rDoc.MaxCol()); + if (Ref2.IsColRel()) + Ref2.SetRelCol( nCol - rPos.Col()); + else + Ref2.SetAbsCol( nCol); + } + else + Ref2.IncCol( nDelta); // was greater than rDoc.MaxCol(), caller should know... + + return true; +} + +bool ScComplexRefData::IncEndRowSticky( const ScDocument& rDoc, SCROW nDelta, const ScAddress& rPos ) +{ + SCROW nRow1 = Ref1.IsRowRel() ? Ref1.Row() + rPos.Row() : Ref1.Row(); + SCROW nRow2 = Ref2.IsRowRel() ? Ref2.Row() + rPos.Row() : Ref2.Row(); + if (nRow1 >= nRow2) + { + // Less than two rows => not sticky. + Ref2.IncRow( nDelta); + return true; + } + + if (nRow2 == rDoc.MaxRow()) + // already sticky + return false; + + if (nRow2 < rDoc.MaxRow()) + { + SCROW nRow = ::std::min( static_cast<SCROW>(nRow2 + nDelta), rDoc.MaxRow()); + if (Ref2.IsRowRel()) + Ref2.SetRelRow( nRow - rPos.Row()); + else + Ref2.SetAbsRow( nRow); + } + else + Ref2.IncRow( nDelta); // was greater than rDoc.MaxRow(), caller should know... + + return true; +} + +bool ScComplexRefData::IsDeleted() const +{ + return Ref1.IsDeleted() || Ref2.IsDeleted(); +} + +#if DEBUG_FORMULA_COMPILER +void ScComplexRefData::Dump( int nIndent ) const +{ + std::string aIndent; + for (int i = 0; i < nIndent; ++i) + aIndent += " "; + + cout << aIndent << "ref 1" << endl; + Ref1.Dump(nIndent+1); + cout << aIndent << "ref 2" << endl; + Ref2.Dump(nIndent+1); +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/reffind.cxx b/sc/source/core/tool/reffind.cxx new file mode 100644 index 0000000000..10522310f8 --- /dev/null +++ b/sc/source/core/tool/reffind.cxx @@ -0,0 +1,334 @@ +/* -*- 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 <sal/config.h> + +#include <o3tl/underlyingenumvalue.hxx> + +#include <reffind.hxx> +#include <global.hxx> +#include <compiler.hxx> +#include <document.hxx> +#include <utility> + +namespace { + +// Include colon; addresses in range reference are handled individually. +const sal_Unicode pDelimiters[] = { + '=','(',')','+','-','*','/','^','&',' ','{','}','<','>',':', 0 +}; + +bool IsText( sal_Unicode c ) +{ + bool bFound = ScGlobal::UnicodeStrChr( pDelimiters, c ); + if (bFound) + // This is one of delimiters, therefore not text. + return false; + + // argument separator is configurable. + const sal_Unicode sep = ScCompiler::GetNativeSymbolChar(ocSep); + return c != sep; +} + +bool IsText( bool& bQuote, sal_Unicode c ) +{ + if (c == '\'') + { + bQuote = !bQuote; + return true; + } + if (bQuote) + return true; + + return IsText(c); +} + +/** + * Find first character position that is considered text. A character is + * considered a text when it's within the ascii range and when it's not a + * delimiter. + */ +sal_Int32 FindStartPos(const sal_Unicode* p, sal_Int32 nStartPos, sal_Int32 nEndPos) +{ + while (nStartPos <= nEndPos && !IsText(p[nStartPos])) + ++nStartPos; + + return nStartPos; +} + +sal_Int32 FindEndPosA1(const sal_Unicode* p, sal_Int32 nStartPos, sal_Int32 nEndPos) +{ + bool bQuote = false; + sal_Int32 nNewEnd = nStartPos; + while (nNewEnd <= nEndPos && IsText(bQuote, p[nNewEnd])) + ++nNewEnd; + + return nNewEnd; +} + +sal_Int32 FindEndPosR1C1(const sal_Unicode* p, sal_Int32 nStartPos, sal_Int32 nEndPos) +{ + sal_Int32 nNewEnd = nStartPos; + p = &p[nStartPos]; + for (; nNewEnd <= nEndPos; ++p, ++nNewEnd) + { + if (*p == '\'') + { + // Skip until the closing quote. + for (++p, ++nNewEnd; nNewEnd <= nEndPos; ++p, ++nNewEnd) + if (*p == '\'') + break; + if (nNewEnd > nEndPos) + break; + } + else if (*p == '[') + { + // Skip until the closing bracket. + for (++p, ++nNewEnd; nNewEnd <= nEndPos; ++p, ++nNewEnd) + if (*p == ']') + break; + if (nNewEnd > nEndPos) + break; + } + else if (!IsText(*p)) + break; + } + + return nNewEnd; +} + +/** + * Find last character position that is considered text, from the specified + * start position. + */ +sal_Int32 FindEndPos(const sal_Unicode* p, sal_Int32 nStartPos, sal_Int32 nEndPos, + formula::FormulaGrammar::AddressConvention eConv) +{ + switch (eConv) + { + case formula::FormulaGrammar::CONV_XL_R1C1: + return FindEndPosR1C1(p, nStartPos, nEndPos); + case formula::FormulaGrammar::CONV_OOO: + case formula::FormulaGrammar::CONV_XL_A1: + default: + return FindEndPosA1(p, nStartPos, nEndPos); + } +} + +void ExpandToTextA1(const sal_Unicode* p, sal_Int32 nLen, sal_Int32& rStartPos, sal_Int32& rEndPos) +{ + bool bQuote = false; // skip quoted text + while (rStartPos > 0 && IsText(bQuote, p[rStartPos - 1]) ) + --rStartPos; + if (rEndPos) + --rEndPos; + while (rEndPos+1 < nLen && IsText(p[rEndPos + 1]) ) + ++rEndPos; +} + +void ExpandToTextR1C1(const sal_Unicode* p, sal_Int32 nLen, sal_Int32& rStartPos, sal_Int32& rEndPos) +{ + // move back the start position to the first text character. + if (rStartPos > 0) + { + for (--rStartPos; rStartPos > 0; --rStartPos) + { + sal_Unicode c = p[rStartPos]; + if (c == '\'') + { + // Skip until the opening quote. + for (--rStartPos; rStartPos > 0; --rStartPos) + { + c = p[rStartPos]; + if (c == '\'') + break; + } + if (rStartPos == 0) + break; + } + else if (c == ']') + { + // Skip until the opening bracket. + for (--rStartPos; rStartPos > 0; --rStartPos) + { + c = p[rStartPos]; + if (c == '[') + break; + } + if (rStartPos == 0) + break; + } + else if (!IsText(c)) + { + ++rStartPos; + break; + } + } + } + + // move forward the end position to the last text character. + rEndPos = FindEndPosR1C1(p, rEndPos, nLen-1); +} + +void ExpandToText(const sal_Unicode* p, sal_Int32 nLen, sal_Int32& rStartPos, sal_Int32& rEndPos, + formula::FormulaGrammar::AddressConvention eConv) +{ + switch (eConv) + { + case formula::FormulaGrammar::CONV_XL_R1C1: + ExpandToTextR1C1(p, nLen, rStartPos, rEndPos); + break; + case formula::FormulaGrammar::CONV_OOO: + case formula::FormulaGrammar::CONV_XL_A1: + default: + ExpandToTextA1(p, nLen, rStartPos, rEndPos); + } +} + +} + +ScRefFinder::ScRefFinder( + OUString aFormula, const ScAddress& rPos, + ScDocument& rDoc, formula::FormulaGrammar::AddressConvention eConvP) : + maFormula(std::move(aFormula)), + meConv(eConvP), + mrDoc(rDoc), + maPos(rPos), + mnFound(0), + mnSelStart(0), + mnSelEnd(0) +{ +} + +ScRefFinder::~ScRefFinder() +{ +} + +static ScRefFlags lcl_NextFlags( ScRefFlags nOld ) +{ + const ScRefFlags Mask_ABS = ScRefFlags::COL_ABS | ScRefFlags::ROW_ABS | ScRefFlags::TAB_ABS; + ScRefFlags nNew = nOld & Mask_ABS; + nNew = ScRefFlags( o3tl::to_underlying(nNew) - 1 ) & Mask_ABS; // weiterzaehlen + + if (!(nOld & ScRefFlags::TAB_3D)) + nNew &= ~ScRefFlags::TAB_ABS; // not 3D -> never absolute! + + return (nOld & ~Mask_ABS) | nNew; +} + +void ScRefFinder::ToggleRel( sal_Int32 nStartPos, sal_Int32 nEndPos ) +{ + sal_Int32 nLen = maFormula.getLength(); + if (nLen <= 0) + return; + const sal_Unicode* pSource = maFormula.getStr(); // for quick access + + // expand selection, and instead of selection start- and end-index + + if ( nEndPos < nStartPos ) + ::std::swap(nEndPos, nStartPos); + + ExpandToText(pSource, nLen, nStartPos, nEndPos, meConv); + + OUStringBuffer aResult; + OUString aExpr; + OUString aSep; + ScAddress aAddr; + mnFound = 0; + + sal_Int32 nLoopStart = nStartPos; + while ( nLoopStart <= nEndPos ) + { + // Determine the start and end positions of a text segment. Note that + // the end position returned from FindEndPos may be one position after + // the last character position in case of the last segment. + sal_Int32 nEStart = FindStartPos(pSource, nLoopStart, nEndPos); + sal_Int32 nEEnd = FindEndPos(pSource, nEStart, nEndPos, meConv); + + aSep = maFormula.copy(nLoopStart, nEStart-nLoopStart); + if (nEEnd < maFormula.getLength()) + aExpr = maFormula.copy(nEStart, nEEnd-nEStart); + else + aExpr = maFormula.copy(nEStart); + + // Check the validity of the expression, and toggle the relative flag. + ScAddress::Details aDetails(meConv, maPos.Row(), maPos.Col()); + ScAddress::ExternalInfo aExtInfo; + ScRefFlags nResult = aAddr.Parse(aExpr, mrDoc, aDetails, &aExtInfo); + if ( nResult & ScRefFlags::VALID ) + { + ScRefFlags nFlags; + if( aExtInfo.mbExternal ) + { // retain external doc name and tab name before toggle relative flag + sal_Int32 nSep; + switch(meConv) + { + case formula::FormulaGrammar::CONV_XL_A1 : + case formula::FormulaGrammar::CONV_XL_OOX : + case formula::FormulaGrammar::CONV_XL_R1C1 : + nSep = aExpr.lastIndexOf('!'); + break; + case formula::FormulaGrammar::CONV_OOO : + default: + nSep = aExpr.lastIndexOf('.'); + break; + } + if (nSep >= 0) + { + OUString aRef = aExpr.copy(nSep+1); + std::u16string_view aExtDocNameTabName = aExpr.subView(0, nSep+1); + nResult = aAddr.Parse(aRef, mrDoc, aDetails); + aAddr.SetTab(0); // force to first tab to avoid error on checking + nFlags = lcl_NextFlags( nResult ); + aExpr = aExtDocNameTabName + aAddr.Format(nFlags, &mrDoc, aDetails); + } + else + { + assert(!"Invalid syntax according to address convention."); + } + } + else + { + nFlags = lcl_NextFlags( nResult ); + aExpr = aAddr.Format(nFlags, &mrDoc, aDetails); + } + + sal_Int32 nAbsStart = nStartPos+aResult.getLength()+aSep.getLength(); + + if (!mnFound) // first reference ? + mnSelStart = nAbsStart; + mnSelEnd = nAbsStart + aExpr.getLength(); // selection, no indices + ++mnFound; + } + + // assemble + + aResult.append(aSep + aExpr); + + nLoopStart = nEEnd; + } + + OUString aTotal = maFormula.subView(0, nStartPos) + aResult; + if (nEndPos < maFormula.getLength()-1) + aTotal += maFormula.subView(nEndPos+1); + + maFormula = aTotal; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/refhint.cxx b/sc/source/core/tool/refhint.cxx new file mode 100644 index 0000000000..a7245fd281 --- /dev/null +++ b/sc/source/core/tool/refhint.cxx @@ -0,0 +1,80 @@ +/* -*- 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/. + */ + +#include <refhint.hxx> + +namespace sc { + +RefHint::RefHint( Type eType ) : SfxHint(SfxHintId::ScReference), meType(eType) {} +RefHint::~RefHint() {} + +RefHint::Type RefHint::getType() const +{ + return meType; +} + +RefColReorderHint::RefColReorderHint( const sc::ColRowReorderMapType& rColMap, SCTAB nTab, SCROW nRow1, SCROW nRow2 ) : + RefHint(ColumnReordered), mrColMap(rColMap), mnTab(nTab), mnRow1(nRow1), mnRow2(nRow2) {} + +RefColReorderHint::~RefColReorderHint() {} + +const sc::ColRowReorderMapType& RefColReorderHint::getColMap() const +{ + return mrColMap; +} + +SCTAB RefColReorderHint::getTab() const +{ + return mnTab; +} + +SCROW RefColReorderHint::getStartRow() const +{ + return mnRow1; +} + +SCROW RefColReorderHint::getEndRow() const +{ + return mnRow2; +} + +RefRowReorderHint::RefRowReorderHint( const sc::ColRowReorderMapType& rRowMap, SCTAB nTab, SCCOL nCol1, SCCOL nCol2 ) : + RefHint(RowReordered), mrRowMap(rRowMap), mnTab(nTab), mnCol1(nCol1), mnCol2(nCol2) {} + +RefRowReorderHint::~RefRowReorderHint() {} + +const sc::ColRowReorderMapType& RefRowReorderHint::getRowMap() const +{ + return mrRowMap; +} + +SCTAB RefRowReorderHint::getTab() const +{ + return mnTab; +} + +SCCOL RefRowReorderHint::getStartColumn() const +{ + return mnCol1; +} + +SCCOL RefRowReorderHint::getEndColumn() const +{ + return mnCol2; +} + +RefStartListeningHint::RefStartListeningHint() : RefHint(StartListening) {} +RefStartListeningHint::~RefStartListeningHint() {} + +RefStopListeningHint::RefStopListeningHint() : RefHint(StopListening) {} +RefStopListeningHint::~RefStopListeningHint() {} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/refreshtimer.cxx b/sc/source/core/tool/refreshtimer.cxx new file mode 100644 index 0000000000..1ad6734d9c --- /dev/null +++ b/sc/source/core/tool/refreshtimer.cxx @@ -0,0 +1,140 @@ +/* -*- 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 <refreshtimer.hxx> +#include <refreshtimerprotector.hxx> + +void ScRefreshTimerControl::SetAllowRefresh( bool b ) +{ + if ( b && nBlockRefresh ) + --nBlockRefresh; + else if ( !b && nBlockRefresh < sal_uInt16(~0) ) + ++nBlockRefresh; +} + +ScRefreshTimerProtector::ScRefreshTimerProtector( std::unique_ptr<ScRefreshTimerControl> const & rp ) + : + m_rpControl( rp ) +{ + if ( m_rpControl ) + { + m_rpControl->SetAllowRefresh( false ); + // wait for any running refresh in another thread to finish + std::scoped_lock aGuard( m_rpControl->GetMutex() ); + } +} + +ScRefreshTimerProtector::~ScRefreshTimerProtector() +{ + if ( m_rpControl ) + m_rpControl->SetAllowRefresh( true ); +} + +ScRefreshTimer::ScRefreshTimer() : AutoTimer("ScRefreshTimer"), ppControl(nullptr) +{ + SetTimeout( 0 ); +} + +ScRefreshTimer::ScRefreshTimer( sal_Int32 nRefreshDelaySeconds ) : AutoTimer("ScRefreshTimer"), ppControl(nullptr) +{ + SetTimeout( nRefreshDelaySeconds * 1000 ); + Launch(); +} + +ScRefreshTimer::ScRefreshTimer( const ScRefreshTimer& r ) : AutoTimer( r ), ppControl(nullptr) +{ +} + +ScRefreshTimer::~ScRefreshTimer() +{ + if ( IsActive() ) + Stop(); +} + +ScRefreshTimer& ScRefreshTimer::operator=( const ScRefreshTimer& r ) +{ + if(this == &r) + return *this; + + SetRefreshControl(nullptr); + AutoTimer::operator=( r ); + return *this; +} + +bool ScRefreshTimer::operator==( const ScRefreshTimer& r ) const +{ + return GetTimeout() == r.GetTimeout(); +} + +bool ScRefreshTimer::operator!=( const ScRefreshTimer& r ) const +{ + return !ScRefreshTimer::operator==( r ); +} + +void ScRefreshTimer::SetRefreshControl( std::unique_ptr<ScRefreshTimerControl> const * pp ) +{ + ppControl = pp; +} + +void ScRefreshTimer::SetRefreshHandler( const Link<Timer *, void>& rLink ) +{ + SetInvokeHandler( rLink ); +} + +sal_Int32 ScRefreshTimer::GetRefreshDelaySeconds() const +{ + return GetTimeout() / 1000; +} + +void ScRefreshTimer::StopRefreshTimer() +{ + Stop(); +} + +void ScRefreshTimer::SetRefreshDelay( sal_Int32 nSeconds ) +{ + bool bActive = IsActive(); + if ( bActive && !nSeconds ) + Stop(); + SetTimeout( nSeconds * 1000 ); + if ( !bActive && nSeconds ) + Launch(); +} + +void ScRefreshTimer::Invoke() +{ + if ( ppControl && *ppControl && (*ppControl)->IsRefreshAllowed() ) + { + // now we COULD make the call in another thread ... + std::scoped_lock aGuard( (*ppControl)->GetMutex() ); + Timer::Invoke(); + // restart from now on, don't execute immediately again if timed out + // a second time during refresh + if ( IsActive() ) + Launch(); + } +} + +void ScRefreshTimer::Launch() +{ + if ( GetTimeout() ) + AutoTimer::Start(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/reftokenhelper.cxx b/sc/source/core/tool/reftokenhelper.cxx new file mode 100644 index 0000000000..a4992485ee --- /dev/null +++ b/sc/source/core/tool/reftokenhelper.cxx @@ -0,0 +1,486 @@ +/* -*- 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 <reftokenhelper.hxx> +#include <document.hxx> +#include <rangeutl.hxx> +#include <compiler.hxx> +#include <tokenarray.hxx> + +#include <rtl/ustring.hxx> +#include <formula/grammar.hxx> +#include <formula/token.hxx> + +#include <memory> + +using namespace formula; + +using ::std::vector; + +void ScRefTokenHelper::compileRangeRepresentation( + vector<ScTokenRef>& rRefTokens, const OUString& rRangeStr, ScDocument& rDoc, + const sal_Unicode cSep, FormulaGrammar::Grammar eGrammar, bool bOnly3DRef) +{ + // #i107275# ignore parentheses + OUString aRangeStr = rRangeStr; + while( (aRangeStr.getLength() >= 2) && (aRangeStr[ 0 ] == '(') && (aRangeStr[ aRangeStr.getLength() - 1 ] == ')') ) + aRangeStr = aRangeStr.copy( 1, aRangeStr.getLength() - 2 ); + + bool bFailure = false; + sal_Int32 nOffset = 0; + while (nOffset >= 0 && !bFailure) + { + OUString aToken; + ScRangeStringConverter::GetTokenByOffset(aToken, aRangeStr, nOffset, cSep); + if (nOffset < 0) + break; + + ScCompiler aCompiler(rDoc, ScAddress(0,0,0), eGrammar); + std::unique_ptr<ScTokenArray> pArray(aCompiler.CompileString(aToken)); + + // There MUST be exactly one reference per range token and nothing + // else, and it MUST be a valid reference, not some #REF! + sal_uInt16 nLen = pArray->GetLen(); + if (!nLen) + continue; // Should a missing range really be allowed? + if (nLen != 1) + { + bFailure = true; + break; + } + + const FormulaToken* p = pArray->FirstToken(); + if (!p) + { + bFailure = true; + break; + } + + switch (p->GetType()) + { + case svSingleRef: + { + const ScSingleRefData& rRef = *p->GetSingleRef(); + if (!rRef.Valid(rDoc)) + bFailure = true; + else if (bOnly3DRef && !rRef.IsFlag3D()) + bFailure = true; + } + break; + case svDoubleRef: + { + const ScComplexRefData& rRef = *p->GetDoubleRef(); + if (!rRef.Valid(rDoc)) + bFailure = true; + else if (bOnly3DRef && !rRef.Ref1.IsFlag3D()) + bFailure = true; + } + break; + case svExternalSingleRef: + { + if (!p->GetSingleRef()->ValidExternal(rDoc)) + bFailure = true; + } + break; + case svExternalDoubleRef: + { + if (!p->GetDoubleRef()->ValidExternal(rDoc)) + bFailure = true; + } + break; + case svString: + if (p->GetString().isEmpty()) + bFailure = true; + break; + case svIndex: + { + if (p->GetOpCode() == ocName) + { + ScRangeData* pNameRange = rDoc.FindRangeNameBySheetAndIndex(p->GetSheet(), p->GetIndex()); + if (!pNameRange->HasReferences()) + bFailure = true; + } + } + break; + default: + bFailure = true; + break; + } + if (!bFailure) + rRefTokens.emplace_back(p->Clone()); + + } + if (bFailure) + rRefTokens.clear(); +} + +bool ScRefTokenHelper::getRangeFromToken( + const ScDocument* pDoc, + ScRange& rRange, const ScTokenRef& pToken, const ScAddress& rPos, bool bExternal) +{ + StackVar eType = pToken->GetType(); + switch (pToken->GetType()) + { + case svSingleRef: + case svExternalSingleRef: + { + if ((eType == svExternalSingleRef && !bExternal) || + (eType == svSingleRef && bExternal)) + return false; + + const ScSingleRefData& rRefData = *pToken->GetSingleRef(); + rRange.aStart = rRefData.toAbs(*pDoc, rPos); + rRange.aEnd = rRange.aStart; + return true; + } + case svDoubleRef: + case svExternalDoubleRef: + { + if ((eType == svExternalDoubleRef && !bExternal) || + (eType == svDoubleRef && bExternal)) + return false; + + const ScComplexRefData& rRefData = *pToken->GetDoubleRef(); + rRange = rRefData.toAbs(*pDoc, rPos); + return true; + } + case svIndex: + { + if (pToken->GetOpCode() == ocName) + { + ScRangeData* pNameRange = pDoc->FindRangeNameBySheetAndIndex(pToken->GetSheet(), pToken->GetIndex()); + if (pNameRange->IsReference(rRange, rPos)) + return true; + } + return false; + } + default: + ; // do nothing + } + return false; +} + +void ScRefTokenHelper::getRangeListFromTokens( + const ScDocument* pDoc, ScRangeList& rRangeList, const vector<ScTokenRef>& rTokens, const ScAddress& rPos) +{ + for (const auto& rToken : rTokens) + { + ScRange aRange; + getRangeFromToken(pDoc, aRange, rToken, rPos); + rRangeList.push_back(aRange); + } +} + +void ScRefTokenHelper::getTokenFromRange(const ScDocument* pDoc, ScTokenRef& pToken, const ScRange& rRange) +{ + ScComplexRefData aData; + aData.InitRange(rRange); + aData.Ref1.SetFlag3D(true); + + // Display sheet name on 2nd reference only when the 1st and 2nd refs are on + // different sheets. + aData.Ref2.SetFlag3D(rRange.aStart.Tab() != rRange.aEnd.Tab()); + + pToken.reset(new ScDoubleRefToken(pDoc->GetSheetLimits(), aData)); +} + +void ScRefTokenHelper::getTokensFromRangeList(const ScDocument* pDoc, vector<ScTokenRef>& pTokens, const ScRangeList& rRanges) +{ + vector<ScTokenRef> aTokens; + size_t nCount = rRanges.size(); + aTokens.reserve(nCount); + for (size_t i = 0; i < nCount; ++i) + { + const ScRange & rRange = rRanges[i]; + ScTokenRef pToken; + ScRefTokenHelper::getTokenFromRange(pDoc, pToken, rRange); + aTokens.push_back(pToken); + } + pTokens.swap(aTokens); +} + +bool ScRefTokenHelper::isRef(const ScTokenRef& pToken) +{ + switch (pToken->GetType()) + { + case svSingleRef: + case svDoubleRef: + case svExternalSingleRef: + case svExternalDoubleRef: + return true; + default: + ; + } + return false; +} + +bool ScRefTokenHelper::isExternalRef(const ScTokenRef& pToken) +{ + switch (pToken->GetType()) + { + case svExternalSingleRef: + case svExternalDoubleRef: + return true; + default: + ; + } + return false; +} + +bool ScRefTokenHelper::intersects( + const ScDocument* pDoc, + const vector<ScTokenRef>& rTokens, const ScTokenRef& pToken, const ScAddress& rPos) +{ + if (!isRef(pToken)) + return false; + + bool bExternal = isExternalRef(pToken); + sal_uInt16 nFileId = bExternal ? pToken->GetIndex() : 0; + + ScRange aRange; + getRangeFromToken(pDoc, aRange, pToken, rPos, bExternal); + + for (const ScTokenRef& p : rTokens) + { + if (!isRef(p)) + continue; + + if (bExternal != isExternalRef(p)) + continue; + + ScRange aRange2; + getRangeFromToken(pDoc, aRange2, p, rPos, bExternal); + + if (bExternal && nFileId != p->GetIndex()) + // different external file + continue; + + if (aRange.Intersects(aRange2)) + return true; + } + return false; +} + +namespace { + +class JoinRefTokenRanges +{ +public: + /** + * Insert a new reference token into the existing list of reference tokens, + * but in that process, try to join as many adjacent ranges as possible. + * + * @param rTokens existing list of reference tokens + * @param rToken new token + */ + void operator() (const ScDocument* pDoc, vector<ScTokenRef>& rTokens, const ScTokenRef& pToken, const ScAddress& rPos) + { + join(pDoc, rTokens, pToken, rPos); + } + +private: + + /** + * Check two 1-dimensional ranges to see if they overlap each other. + * + * @param nMin1 min value of range 1 + * @param nMax1 max value of range 1 + * @param nMin2 min value of range 2 + * @param nMax2 max value of range 2 + * @param rNewMin min value of new range in case they overlap + * @param rNewMax max value of new range in case they overlap + */ + template<typename T> + static bool overlaps(T nMin1, T nMax1, T nMin2, T nMax2, T& rNewMin, T& rNewMax) + { + bool bDisjoint1 = (nMin1 > nMax2) && (nMin1 - nMax2 > 1); + bool bDisjoint2 = (nMin2 > nMax1) && (nMin2 - nMax1 > 1); + if (bDisjoint1 || bDisjoint2) + // These two ranges cannot be joined. Move on. + return false; + + T nMin = std::min(nMin1, nMin2); + T nMax = std::max(nMax1, nMax2); + + rNewMin = nMin; + rNewMax = nMax; + + return true; + } + + void join(const ScDocument* pDoc, vector<ScTokenRef>& rTokens, const ScTokenRef& pToken, const ScAddress& rPos) + { + // Normalize the token to a double reference. + ScComplexRefData aData; + if (!ScRefTokenHelper::getDoubleRefDataFromToken(aData, pToken)) + return; + + // Get the information of the new token. + bool bExternal = ScRefTokenHelper::isExternalRef(pToken); + sal_uInt16 nFileId = bExternal ? pToken->GetIndex() : 0; + svl::SharedString aTabName = bExternal ? pToken->GetString() : svl::SharedString::getEmptyString(); + + bool bJoined = false; + for (ScTokenRef& pOldToken : rTokens) + { + if (!ScRefTokenHelper::isRef(pOldToken)) + // A non-ref token should not have been added here in the first + // place! + continue; + + if (bExternal != ScRefTokenHelper::isExternalRef(pOldToken)) + // External and internal refs don't mix. + continue; + + if (bExternal) + { + if (nFileId != pOldToken->GetIndex()) + // Different external files. + continue; + + if (aTabName != pOldToken->GetString()) + // Different table names. + continue; + } + + ScComplexRefData aOldData; + if (!ScRefTokenHelper::getDoubleRefDataFromToken(aOldData, pOldToken)) + continue; + + ScRange aOld = aOldData.toAbs(*pDoc, rPos), aNew = aData.toAbs(*pDoc, rPos); + + if (aNew.aStart.Tab() != aOld.aStart.Tab() || aNew.aEnd.Tab() != aOld.aEnd.Tab()) + // Sheet ranges differ. + continue; + + if (aOld.Contains(aNew)) + // This new range is part of an existing range. Skip it. + return; + + bool bSameRows = (aNew.aStart.Row() == aOld.aStart.Row()) && (aNew.aEnd.Row() == aOld.aEnd.Row()); + bool bSameCols = (aNew.aStart.Col() == aOld.aStart.Col()) && (aNew.aEnd.Col() == aOld.aEnd.Col()); + ScComplexRefData aNewData = aOldData; + bool bJoinRanges = false; + if (bSameRows) + { + SCCOL nNewMin, nNewMax; + bJoinRanges = overlaps( + aNew.aStart.Col(), aNew.aEnd.Col(), aOld.aStart.Col(), aOld.aEnd.Col(), + nNewMin, nNewMax); + + if (bJoinRanges) + { + aNew.aStart.SetCol(nNewMin); + aNew.aEnd.SetCol(nNewMax); + aNewData.SetRange(pDoc->GetSheetLimits(), aNew, rPos); + } + } + else if (bSameCols) + { + SCROW nNewMin, nNewMax; + bJoinRanges = overlaps( + aNew.aStart.Row(), aNew.aEnd.Row(), aOld.aStart.Row(), aOld.aEnd.Row(), + nNewMin, nNewMax); + + if (bJoinRanges) + { + aNew.aStart.SetRow(nNewMin); + aNew.aEnd.SetRow(nNewMax); + aNewData.SetRange(pDoc->GetSheetLimits(), aNew, rPos); + } + } + + if (bJoinRanges) + { + if (bExternal) + pOldToken.reset(new ScExternalDoubleRefToken(nFileId, aTabName, aNewData)); + else + pOldToken.reset(new ScDoubleRefToken(pDoc->GetSheetLimits(), aNewData)); + + bJoined = true; + break; + } + } + + if (bJoined) + { + if (rTokens.size() == 1) + // There is only one left. No need to do more joining. + return; + + // Pop the last token from the list, and keep joining recursively. + ScTokenRef p = rTokens.back(); + rTokens.pop_back(); + join(pDoc, rTokens, p, rPos); + } + else + rTokens.push_back(pToken); + } +}; + +} + +void ScRefTokenHelper::join(const ScDocument* pDoc, vector<ScTokenRef>& rTokens, const ScTokenRef& pToken, const ScAddress& rPos) +{ + JoinRefTokenRanges join; + join(pDoc, rTokens, pToken, rPos); +} + +bool ScRefTokenHelper::getDoubleRefDataFromToken(ScComplexRefData& rData, const ScTokenRef& pToken) +{ + switch (pToken->GetType()) + { + case svSingleRef: + case svExternalSingleRef: + { + const ScSingleRefData& r = *pToken->GetSingleRef(); + rData.Ref1 = r; + rData.Ref1.SetFlag3D(true); + rData.Ref2 = r; + rData.Ref2.SetFlag3D(false); // Don't display sheet name on second reference. + } + break; + case svDoubleRef: + case svExternalDoubleRef: + rData = *pToken->GetDoubleRef(); + break; + default: + // Not a reference token. Bail out. + return false; + } + return true; +} + +ScTokenRef ScRefTokenHelper::createRefToken(const ScDocument& rDoc, const ScAddress& rAddr) +{ + ScSingleRefData aRefData; + aRefData.InitAddress(rAddr); + ScTokenRef pRef(new ScSingleRefToken(rDoc.GetSheetLimits(), aRefData)); + return pRef; +} + +ScTokenRef ScRefTokenHelper::createRefToken(const ScDocument& rDoc, const ScRange& rRange) +{ + ScComplexRefData aRefData; + aRefData.InitRange(rRange); + ScTokenRef pRef(new ScDoubleRefToken(rDoc.GetSheetLimits(), aRefData)); + return pRef; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/refupdat.cxx b/sc/source/core/tool/refupdat.cxx new file mode 100644 index 0000000000..95f738c4ed --- /dev/null +++ b/sc/source/core/tool/refupdat.cxx @@ -0,0 +1,592 @@ +/* -*- 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 <refupdat.hxx> +#include <document.hxx> +#include <bigrange.hxx> +#include <refdata.hxx> + +#include <osl/diagnose.h> + +template< typename R, typename S, typename U > +static bool lcl_MoveStart( R& rRef, U nStart, S nDelta, U nMask, bool bShrink = true ) +{ + bool bCut = false; + if ( rRef >= nStart ) + rRef = sal::static_int_cast<R>( rRef + nDelta ); + else if ( nDelta < 0 && bShrink && rRef >= nStart + nDelta ) + rRef = nStart + nDelta; //TODO: limit ??? + if ( rRef < 0 ) + { + rRef = 0; + bCut = true; + } + else if ( rRef > nMask ) + { + rRef = nMask; + bCut = true; + } + return bCut; +} + +template< typename R, typename S, typename U > +static bool lcl_MoveEnd( R& rRef, U nStart, S nDelta, U nMask, bool bShrink = true ) +{ + bool bCut = false; + if ( rRef >= nStart ) + rRef = sal::static_int_cast<R>( rRef + nDelta ); + else if ( nDelta < 0 && bShrink && rRef >= nStart + nDelta ) + rRef = nStart + nDelta - 1; //TODO: limit ??? + if (rRef < 0) + { + rRef = 0; + bCut = true; + } + else if(rRef > nMask) + { + rRef = nMask; + bCut = true; + } + return bCut; +} + +template< typename R, typename S, typename U > +static bool lcl_MoveReorder( R& rRef, U nStart, U nEnd, S nDelta ) +{ + if ( rRef >= nStart && rRef <= nEnd ) + { + rRef = sal::static_int_cast<R>( rRef + nDelta ); + return true; + } + + if ( nDelta > 0 ) // move backward + { + if ( rRef >= nStart && rRef <= nEnd + nDelta ) + { + if ( rRef <= nEnd ) + rRef = sal::static_int_cast<R>( rRef + nDelta ); // in the moved range + else + rRef -= nEnd - nStart + 1; // move up + return true; + } + } + else // move forward + { + if ( rRef >= nStart + nDelta && rRef <= nEnd ) + { + if ( rRef >= nStart ) + rRef = sal::static_int_cast<R>( rRef + nDelta ); // in the moved range + else + rRef += nEnd - nStart + 1; // move up + return true; + } + } + + return false; +} + +template< typename R, typename S, typename U > +static bool lcl_MoveItCut( R& rRef, S nDelta, U nMask ) +{ + bool bCut = false; + rRef = sal::static_int_cast<R>( rRef + nDelta ); + if ( rRef < 0 ) + { + rRef = 0; + bCut = true; + } + else if ( rRef > nMask ) + { + rRef = nMask; + bCut = true; + } + return bCut; +} + +template< typename R, typename U > +static void lcl_MoveItWrap( R& rRef, U nMask ) +{ + rRef = sal::static_int_cast<R>( rRef ); + if ( rRef < 0 ) + rRef += nMask+1; + else if ( rRef > nMask ) + rRef -= nMask+1; +} + +template< typename R, typename S, typename U > +static bool IsExpand( R n1, R n2, U nStart, S nD ) +{ // before normal Move... + return + nD > 0 // Insert + && n1 < n2 // at least two Cols/Rows/Tabs in Ref + && ( + (nStart <= n1 && n1 < nStart + nD) // n1 within the Insert + || (n2 + 1 == nStart) // n2 directly before Insert + ); // n1 < nStart <= n2 is expanded anyway! +} + +template< typename R, typename S, typename U > +static void Expand( R& n1, R& n2, U nStart, S nD ) +{ // after normal Move..., only if IsExpand was true before! + // first the End + if ( n2 + 1 == nStart ) + { // at End + n2 = sal::static_int_cast<R>( n2 + nD ); + return; + } + // at the beginning + n1 = sal::static_int_cast<R>( n1 - nD ); +} + +static bool lcl_IsWrapBig( sal_Int64 nRef, sal_Int32 nDelta ) +{ + if ( nRef > 0 && nDelta > 0 ) + return nRef + nDelta <= 0; + else if ( nRef < 0 && nDelta < 0 ) + return nRef + nDelta >= 0; + return false; +} + +static bool lcl_MoveBig( sal_Int64& rRef, sal_Int64 nStart, sal_Int32 nDelta ) +{ + bool bCut = false; + if ( rRef >= nStart ) + { + if ( nDelta > 0 ) + bCut = lcl_IsWrapBig( rRef, nDelta ); + if ( bCut ) + rRef = ScBigRange::nRangeMax; + else + rRef += nDelta; + } + return bCut; +} + +static bool lcl_MoveItCutBig( sal_Int64& rRef, sal_Int32 nDelta ) +{ + bool bCut = lcl_IsWrapBig( rRef, nDelta ); + rRef += nDelta; + return bCut; +} + +ScRefUpdateRes ScRefUpdate::Update( const ScDocument* pDoc, UpdateRefMode eUpdateRefMode, + SCCOL nCol1, SCROW nRow1, SCTAB nTab1, + SCCOL nCol2, SCROW nRow2, SCTAB nTab2, + SCCOL nDx, SCROW nDy, SCTAB nDz, + SCCOL& theCol1, SCROW& theRow1, SCTAB& theTab1, + SCCOL& theCol2, SCROW& theRow2, SCTAB& theTab2 ) +{ + ScRefUpdateRes eRet = UR_NOTHING; + + SCCOL oldCol1 = theCol1; + SCROW oldRow1 = theRow1; + SCTAB oldTab1 = theTab1; + SCCOL oldCol2 = theCol2; + SCROW oldRow2 = theRow2; + SCTAB oldTab2 = theTab2; + + bool bCut1, bCut2; + + if (eUpdateRefMode == URM_INSDEL) + { + bool bExpand = pDoc->IsExpandRefs(); + if ( nDx && (theRow1 >= nRow1) && (theRow2 <= nRow2) && + (theTab1 >= nTab1) && (theTab2 <= nTab2)) + { + bool bExp = (bExpand && IsExpand( theCol1, theCol2, nCol1, nDx )); + bCut1 = lcl_MoveStart( theCol1, nCol1, nDx, pDoc->MaxCol() ); + bCut2 = lcl_MoveEnd( theCol2, nCol1, nDx, pDoc->MaxCol() ); + if ( theCol2 < theCol1 ) + { + eRet = UR_INVALID; + theCol2 = theCol1; + } + else if (bCut2 && theCol2 == 0) + eRet = UR_INVALID; + else if ( bCut1 || bCut2 ) + eRet = UR_UPDATED; + if ( bExp ) + { + Expand( theCol1, theCol2, nCol1, nDx ); + eRet = UR_UPDATED; + } + if (eRet != UR_NOTHING && oldCol1 == 0 && oldCol2 == pDoc->MaxCol()) + { + eRet = UR_STICKY; + theCol1 = oldCol1; + theCol2 = oldCol2; + } + else if (oldCol2 == pDoc->MaxCol() && oldCol1 < pDoc->MaxCol()) + { + // End was sticky, but start may have been moved. Only on range. + theCol2 = oldCol2; + if (eRet == UR_NOTHING) + eRet = UR_STICKY; + } + // Else, if (bCut2 && theCol2 == pDoc->MaxCol()) then end becomes sticky, + // but currently there's nothing to do. + } + if ( nDy && (theCol1 >= nCol1) && (theCol2 <= nCol2) && + (theTab1 >= nTab1) && (theTab2 <= nTab2)) + { + bool bExp = (bExpand && IsExpand( theRow1, theRow2, nRow1, nDy )); + bCut1 = lcl_MoveStart( theRow1, nRow1, nDy, pDoc->MaxRow() ); + bCut2 = lcl_MoveEnd( theRow2, nRow1, nDy, pDoc->MaxRow() ); + if ( theRow2 < theRow1 ) + { + eRet = UR_INVALID; + theRow2 = theRow1; + } + else if (bCut2 && theRow2 == 0) + eRet = UR_INVALID; + else if ( bCut1 || bCut2 ) + eRet = UR_UPDATED; + if ( bExp ) + { + Expand( theRow1, theRow2, nRow1, nDy ); + eRet = UR_UPDATED; + } + if (eRet != UR_NOTHING && oldRow1 == 0 && oldRow2 == pDoc->MaxRow()) + { + eRet = UR_STICKY; + theRow1 = oldRow1; + theRow2 = oldRow2; + } + else if (oldRow2 == pDoc->MaxRow() && oldRow1 < pDoc->MaxRow()) + { + // End was sticky, but start may have been moved. Only on range. + theRow2 = oldRow2; + if (eRet == UR_NOTHING) + eRet = UR_STICKY; + } + // Else, if (bCut2 && theRow2 == pDoc->MaxRow()) then end becomes sticky, + // but currently there's nothing to do. + } + if ( nDz && (theCol1 >= nCol1) && (theCol2 <= nCol2) && + (theRow1 >= nRow1) && (theRow2 <= nRow2) ) + { + SCTAB nMaxTab = pDoc->GetTableCount() - 1; + nMaxTab = sal::static_int_cast<SCTAB>(nMaxTab + nDz); // adjust to new count + bool bExp = (bExpand && IsExpand( theTab1, theTab2, nTab1, nDz )); + bCut1 = lcl_MoveStart( theTab1, nTab1, nDz, nMaxTab, false /*bShrink*/); + bCut2 = lcl_MoveEnd( theTab2, nTab1, nDz, nMaxTab, false /*bShrink*/); + if ( theTab2 < theTab1 ) + { + eRet = UR_INVALID; + theTab2 = theTab1; + } + else if ( bCut1 || bCut2 ) + eRet = UR_UPDATED; + if ( bExp ) + { + Expand( theTab1, theTab2, nTab1, nDz ); + eRet = UR_UPDATED; + } + } + } + else if (eUpdateRefMode == URM_MOVE) + { + if ((theCol1 >= nCol1-nDx) && (theRow1 >= nRow1-nDy) && (theTab1 >= nTab1-nDz) && + (theCol2 <= nCol2-nDx) && (theRow2 <= nRow2-nDy) && (theTab2 <= nTab2-nDz)) + { + if ( nDx ) + { + bCut1 = lcl_MoveItCut( theCol1, nDx, pDoc->MaxCol() ); + bCut2 = lcl_MoveItCut( theCol2, nDx, pDoc->MaxCol() ); + if ( bCut1 || bCut2 ) + eRet = UR_UPDATED; + if (eRet != UR_NOTHING && oldCol1 == 0 && oldCol2 == pDoc->MaxCol()) + { + eRet = UR_STICKY; + theCol1 = oldCol1; + theCol2 = oldCol2; + } + } + if ( nDy ) + { + bCut1 = lcl_MoveItCut( theRow1, nDy, pDoc->MaxRow() ); + bCut2 = lcl_MoveItCut( theRow2, nDy, pDoc->MaxRow() ); + if ( bCut1 || bCut2 ) + eRet = UR_UPDATED; + if (eRet != UR_NOTHING && oldRow1 == 0 && oldRow2 == pDoc->MaxRow()) + { + eRet = UR_STICKY; + theRow1 = oldRow1; + theRow2 = oldRow2; + } + } + if ( nDz ) + { + SCTAB nMaxTab = pDoc->GetTableCount() - 1; + bCut1 = lcl_MoveItCut( theTab1, nDz, nMaxTab ); + bCut2 = lcl_MoveItCut( theTab2, nDz, nMaxTab ); + if ( bCut1 || bCut2 ) + eRet = UR_UPDATED; + } + } + } + else if (eUpdateRefMode == URM_REORDER) + { + // so far only for nDz (MoveTab) + OSL_ENSURE ( !nDx && !nDy, "URM_REORDER for x and y not yet implemented" ); + + if ( nDz && (theCol1 >= nCol1) && (theCol2 <= nCol2) && + (theRow1 >= nRow1) && (theRow2 <= nRow2) ) + { + bCut1 = lcl_MoveReorder( theTab1, nTab1, nTab2, nDz ); + bCut2 = lcl_MoveReorder( theTab2, nTab1, nTab2, nDz ); + if ( bCut1 || bCut2 ) + eRet = UR_UPDATED; + } + } + + if ( eRet == UR_NOTHING ) + { + if (oldCol1 != theCol1 + || oldRow1 != theRow1 + || oldTab1 != theTab1 + || oldCol2 != theCol2 + || oldRow2 != theRow2 + || oldTab2 != theTab2 + ) + eRet = UR_UPDATED; + } + return eRet; +} + +// simple UpdateReference for ScBigRange (ScChangeAction/ScChangeTrack) +// References can also be located outside of the document! +// Whole columns/rows (ScBigRange::nRangeMin..ScBigRange::nRangeMax) stay as such! +ScRefUpdateRes ScRefUpdate::Update( UpdateRefMode eUpdateRefMode, + const ScBigRange& rWhere, sal_Int32 nDx, sal_Int32 nDy, sal_Int32 nDz, + ScBigRange& rWhat ) +{ + ScRefUpdateRes eRet = UR_NOTHING; + const ScBigRange aOldRange( rWhat ); + + sal_Int64 nCol1, nRow1, nTab1, nCol2, nRow2, nTab2; + sal_Int64 theCol1, theRow1, theTab1, theCol2, theRow2, theTab2; + rWhere.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); + rWhat.GetVars( theCol1, theRow1, theTab1, theCol2, theRow2, theTab2 ); + + bool bCut1, bCut2; + + if (eUpdateRefMode == URM_INSDEL) + { + if ( nDx && (theRow1 >= nRow1) && (theRow2 <= nRow2) && + (theTab1 >= nTab1) && (theTab2 <= nTab2) && + (theCol1 != ScBigRange::nRangeMin || theCol2 != ScBigRange::nRangeMax) ) + { + bCut1 = lcl_MoveBig( theCol1, nCol1, nDx ); + bCut2 = lcl_MoveBig( theCol2, nCol1, nDx ); + if ( bCut1 || bCut2 ) + eRet = UR_UPDATED; + rWhat.aStart.SetCol( theCol1 ); + rWhat.aEnd.SetCol( theCol2 ); + } + if ( nDy && (theCol1 >= nCol1) && (theCol2 <= nCol2) && + (theTab1 >= nTab1) && (theTab2 <= nTab2) && + (theRow1 != ScBigRange::nRangeMin || theRow2 != ScBigRange::nRangeMax) ) + { + bCut1 = lcl_MoveBig( theRow1, nRow1, nDy ); + bCut2 = lcl_MoveBig( theRow2, nRow1, nDy ); + if ( bCut1 || bCut2 ) + eRet = UR_UPDATED; + rWhat.aStart.SetRow( theRow1 ); + rWhat.aEnd.SetRow( theRow2 ); + } + if ( nDz && (theCol1 >= nCol1) && (theCol2 <= nCol2) && + (theRow1 >= nRow1) && (theRow2 <= nRow2) && + (theTab1 != ScBigRange::nRangeMin || theTab2 != ScBigRange::nRangeMax) ) + { + bCut1 = lcl_MoveBig( theTab1, nTab1, nDz ); + bCut2 = lcl_MoveBig( theTab2, nTab1, nDz ); + if ( bCut1 || bCut2 ) + eRet = UR_UPDATED; + rWhat.aStart.SetTab( theTab1 ); + rWhat.aEnd.SetTab( theTab2 ); + } + } + else if (eUpdateRefMode == URM_MOVE) + { + if ( rWhere.Contains( rWhat ) ) + { + if ( nDx && (theCol1 != ScBigRange::nRangeMin || theCol2 != ScBigRange::nRangeMax) ) + { + bCut1 = lcl_MoveItCutBig( theCol1, nDx ); + bCut2 = lcl_MoveItCutBig( theCol2, nDx ); + if ( bCut1 || bCut2 ) + eRet = UR_UPDATED; + rWhat.aStart.SetCol( theCol1 ); + rWhat.aEnd.SetCol( theCol2 ); + } + if ( nDy && (theRow1 != ScBigRange::nRangeMin || theRow2 != ScBigRange::nRangeMax) ) + { + bCut1 = lcl_MoveItCutBig( theRow1, nDy ); + bCut2 = lcl_MoveItCutBig( theRow2, nDy ); + if ( bCut1 || bCut2 ) + eRet = UR_UPDATED; + rWhat.aStart.SetRow( theRow1 ); + rWhat.aEnd.SetRow( theRow2 ); + } + if ( nDz && (theTab1 != ScBigRange::nRangeMin || theTab2 != ScBigRange::nRangeMax) ) + { + bCut1 = lcl_MoveItCutBig( theTab1, nDz ); + bCut2 = lcl_MoveItCutBig( theTab2, nDz ); + if ( bCut1 || bCut2 ) + eRet = UR_UPDATED; + rWhat.aStart.SetTab( theTab1 ); + rWhat.aEnd.SetTab( theTab2 ); + } + } + } + + if ( eRet == UR_NOTHING && rWhat != aOldRange ) + eRet = UR_UPDATED; + + return eRet; +} + +void ScRefUpdate::MoveRelWrap( const ScDocument& rDoc, const ScAddress& rPos, + SCCOL nMaxCol, SCROW nMaxRow, ScComplexRefData& rRef ) +{ + ScRange aAbsRange = rRef.toAbs(rDoc, rPos); + if( rRef.Ref1.IsColRel() ) + { + SCCOL nCol = aAbsRange.aStart.Col(); + lcl_MoveItWrap(nCol, nMaxCol); + aAbsRange.aStart.SetCol(nCol); + } + if( rRef.Ref2.IsColRel() ) + { + SCCOL nCol = aAbsRange.aEnd.Col(); + lcl_MoveItWrap(nCol, nMaxCol); + aAbsRange.aEnd.SetCol(nCol); + } + if( rRef.Ref1.IsRowRel() ) + { + SCROW nRow = aAbsRange.aStart.Row(); + lcl_MoveItWrap(nRow, nMaxRow); + aAbsRange.aStart.SetRow(nRow); + } + if( rRef.Ref2.IsRowRel() ) + { + SCROW nRow = aAbsRange.aEnd.Row(); + lcl_MoveItWrap(nRow, nMaxRow); + aAbsRange.aEnd.SetRow(nRow); + } + SCTAB nMaxTab = rDoc.GetTableCount() - 1; + if( rRef.Ref1.IsTabRel() ) + { + SCTAB nTab = aAbsRange.aStart.Tab(); + lcl_MoveItWrap(nTab, nMaxTab); + aAbsRange.aStart.SetTab(nTab); + } + if( rRef.Ref2.IsTabRel() ) + { + SCTAB nTab = aAbsRange.aEnd.Tab(); + lcl_MoveItWrap(nTab, nMaxTab); + aAbsRange.aEnd.SetTab(nTab); + } + + aAbsRange.PutInOrder(); + rRef.SetRange(rDoc.GetSheetLimits(), aAbsRange, rPos); +} + +void ScRefUpdate::DoTranspose( SCCOL& rCol, SCROW& rRow, SCTAB& rTab, + const ScDocument& rDoc, const ScRange& rSource, const ScAddress& rDest ) +{ + SCTAB nDz = rDest.Tab() - rSource.aStart.Tab(); + if (nDz) + { + SCTAB nNewTab = rTab+nDz; + SCTAB nCount = rDoc.GetTableCount(); + while (nNewTab<0) nNewTab = sal::static_int_cast<SCTAB>( nNewTab + nCount ); + while (nNewTab>=nCount) nNewTab = sal::static_int_cast<SCTAB>( nNewTab - nCount ); + rTab = nNewTab; + } + OSL_ENSURE( rCol>=rSource.aStart.Col() && rRow>=rSource.aStart.Row(), + "UpdateTranspose: pos. wrong" ); + + SCCOL nRelX = rCol - rSource.aStart.Col(); + SCROW nRelY = rRow - rSource.aStart.Row(); + + rCol = static_cast<SCCOL>(static_cast<SCCOLROW>(rDest.Col()) + + static_cast<SCCOLROW>(nRelY)); + rRow = static_cast<SCROW>(static_cast<SCCOLROW>(rDest.Row()) + + static_cast<SCCOLROW>(nRelX)); +} + +ScRefUpdateRes ScRefUpdate::UpdateTranspose( + const ScDocument& rDoc, const ScRange& rSource, const ScAddress& rDest, ScRange& rRef ) +{ + ScRefUpdateRes eRet = UR_NOTHING; + // Only references in source range must be updated, i.e. no references in destination area. + // Otherwise existing references pointing to destination area will be wrongly transposed. + if (rSource.Contains(rRef)) + { + // Source range contains the reference range. + SCCOL nCol1 = rRef.aStart.Col(), nCol2 = rRef.aEnd.Col(); + SCROW nRow1 = rRef.aStart.Row(), nRow2 = rRef.aEnd.Row(); + SCTAB nTab1 = rRef.aStart.Tab(), nTab2 = rRef.aEnd.Tab(); + DoTranspose(nCol1, nRow1, nTab1, rDoc, rSource, rDest); + DoTranspose(nCol2, nRow2, nTab2, rDoc, rSource, rDest); + rRef.aStart = ScAddress(nCol1, nRow1, nTab1); + rRef.aEnd = ScAddress(nCol2, nRow2, nTab2); + eRet = UR_UPDATED; + } + return eRet; +} + +// UpdateGrow - expands references which point exactly to the area +// gets by without document + +ScRefUpdateRes ScRefUpdate::UpdateGrow( + const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY, ScRange& rRef ) +{ + ScRefUpdateRes eRet = UR_NOTHING; + + // in y-direction the Ref may also start one row further below, + // if an area contains column heads + + bool bUpdateX = ( nGrowX && + rRef.aStart.Col() == rArea.aStart.Col() && rRef.aEnd.Col() == rArea.aEnd.Col() && + rRef.aStart.Row() >= rArea.aStart.Row() && rRef.aEnd.Row() <= rArea.aEnd.Row() && + rRef.aStart.Tab() >= rArea.aStart.Tab() && rRef.aEnd.Tab() <= rArea.aEnd.Tab() ); + bool bUpdateY = ( nGrowY && + rRef.aStart.Col() >= rArea.aStart.Col() && rRef.aEnd.Col() <= rArea.aEnd.Col() && + (rRef.aStart.Row() == rArea.aStart.Row() || rRef.aStart.Row() == rArea.aStart.Row()+1) && + rRef.aEnd.Row() == rArea.aEnd.Row() && + rRef.aStart.Tab() >= rArea.aStart.Tab() && rRef.aEnd.Tab() <= rArea.aEnd.Tab() ); + + if ( bUpdateX ) + { + rRef.aEnd.SetCol(sal::static_int_cast<SCCOL>(rRef.aEnd.Col() + nGrowX)); + eRet = UR_UPDATED; + } + if ( bUpdateY ) + { + rRef.aEnd.SetRow(sal::static_int_cast<SCROW>(rRef.aEnd.Row() + nGrowY)); + eRet = UR_UPDATED; + } + + return eRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/scmatrix.cxx b/sc/source/core/tool/scmatrix.cxx new file mode 100644 index 0000000000..f62990b76b --- /dev/null +++ b/sc/source/core/tool/scmatrix.cxx @@ -0,0 +1,3645 @@ +/* -*- 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 <scmatrix.hxx> +#include <global.hxx> +#include <address.hxx> +#include <formula/errorcodes.hxx> +#include <interpre.hxx> +#include <mtvelements.hxx> +#include <compare.hxx> +#include <matrixoperators.hxx> +#include <math.hxx> + +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <svl/sharedstring.hxx> +#include <rtl/math.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#include <memory> +#include <mutex> +#include <utility> +#include <vector> +#include <limits> + +#include <mdds/multi_type_matrix.hpp> +#include <mdds/multi_type_vector/types.hpp> + +#if DEBUG_MATRIX +#include <iostream> +using std::cout; +using std::endl; +#endif + +using ::std::pair; +using ::std::advance; + +namespace { + +/** + * Custom string trait struct to tell mdds::multi_type_matrix about the + * custom string type and how to handle blocks storing them. + */ +struct matrix_traits +{ + typedef sc::string_block string_element_block; + typedef sc::uint16_block integer_element_block; +}; + +struct matrix_flag_traits +{ + typedef sc::string_block string_element_block; + typedef mdds::mtv::uint8_element_block integer_element_block; +}; + +} + +typedef mdds::multi_type_matrix<matrix_traits> MatrixImplType; +typedef mdds::multi_type_matrix<matrix_flag_traits> MatrixFlagImplType; + +namespace { + +double convertStringToValue( ScInterpreter* pErrorInterpreter, const OUString& rStr ) +{ + if (pErrorInterpreter) + { + FormulaError nError = FormulaError::NONE; + SvNumFormatType nCurFmtType = SvNumFormatType::ALL; + double fValue = pErrorInterpreter->ConvertStringToValue( rStr, nError, nCurFmtType); + if (nError != FormulaError::NONE) + { + pErrorInterpreter->SetError( nError); + return CreateDoubleError( nError); + } + return fValue; + } + return CreateDoubleError( FormulaError::NoValue); +} + +struct ElemEqualZero +{ + double operator() (double val) const + { + if (!std::isfinite(val)) + return val; + return val == 0.0 ? 1.0 : 0.0; + } +}; + +struct ElemNotEqualZero +{ + double operator() (double val) const + { + if (!std::isfinite(val)) + return val; + return val != 0.0 ? 1.0 : 0.0; + } +}; + +struct ElemGreaterZero +{ + double operator() (double val) const + { + if (!std::isfinite(val)) + return val; + return val > 0.0 ? 1.0 : 0.0; + } +}; + +struct ElemLessZero +{ + double operator() (double val) const + { + if (!std::isfinite(val)) + return val; + return val < 0.0 ? 1.0 : 0.0; + } +}; + +struct ElemGreaterEqualZero +{ + double operator() (double val) const + { + if (!std::isfinite(val)) + return val; + return val >= 0.0 ? 1.0 : 0.0; + } +}; + +struct ElemLessEqualZero +{ + double operator() (double val) const + { + if (!std::isfinite(val)) + return val; + return val <= 0.0 ? 1.0 : 0.0; + } +}; + +template<typename Comp> +class CompareMatrixElemFunc +{ + static Comp maComp; + + std::vector<double> maNewMatValues; // double instead of bool to transport error values + size_t mnRow; + size_t mnCol; +public: + CompareMatrixElemFunc( size_t nRow, size_t nCol ) : mnRow(nRow), mnCol(nCol) + { + maNewMatValues.reserve(nRow*nCol); + } + + CompareMatrixElemFunc( const CompareMatrixElemFunc& ) = delete; + CompareMatrixElemFunc& operator= ( const CompareMatrixElemFunc& ) = delete; + + CompareMatrixElemFunc( CompareMatrixElemFunc&& ) = default; + CompareMatrixElemFunc& operator= ( CompareMatrixElemFunc&& ) = default; + + void operator() (const MatrixImplType::element_block_node_type& node) + { + switch (node.type) + { + case mdds::mtm::element_numeric: + { + typedef MatrixImplType::numeric_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + for (; it != itEnd; ++it) + { + double fVal = *it; + maNewMatValues.push_back(maComp(fVal)); + } + } + break; + case mdds::mtm::element_boolean: + { + typedef MatrixImplType::boolean_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + for (; it != itEnd; ++it) + { + double fVal = *it ? 1.0 : 0.0; + maNewMatValues.push_back(maComp(fVal)); + } + } + break; + case mdds::mtm::element_string: + case mdds::mtm::element_empty: + default: + // Fill it with false. + maNewMatValues.resize(maNewMatValues.size() + node.size, 0.0); + } + } + + void swap( MatrixImplType& rMat ) + { + MatrixImplType aNewMat(mnRow, mnCol, maNewMatValues.begin(), maNewMatValues.end()); + rMat.swap(aNewMat); + } +}; + +template<typename Comp> +Comp CompareMatrixElemFunc<Comp>::maComp; + +} + +typedef uint8_t TMatFlag; +const TMatFlag SC_MATFLAG_EMPTYRESULT = 1; +const TMatFlag SC_MATFLAG_EMPTYPATH = 2; + +class ScMatrixImpl +{ + MatrixImplType maMat; + MatrixFlagImplType maMatFlag; + ScInterpreter* pErrorInterpreter; + +public: + ScMatrixImpl(const ScMatrixImpl&) = delete; + const ScMatrixImpl& operator=(const ScMatrixImpl&) = delete; + + ScMatrixImpl(SCSIZE nC, SCSIZE nR); + ScMatrixImpl(SCSIZE nC, SCSIZE nR, double fInitVal); + + ScMatrixImpl( size_t nC, size_t nR, const std::vector<double>& rInitVals ); + + ~ScMatrixImpl(); + + void Clear(); + void Resize(SCSIZE nC, SCSIZE nR); + void Resize(SCSIZE nC, SCSIZE nR, double fVal); + void SetErrorInterpreter( ScInterpreter* p); + ScInterpreter* GetErrorInterpreter() const { return pErrorInterpreter; } + + void GetDimensions( SCSIZE& rC, SCSIZE& rR) const; + SCSIZE GetElementCount() const; + bool ValidColRow( SCSIZE nC, SCSIZE nR) const; + bool ValidColRowReplicated( SCSIZE & rC, SCSIZE & rR ) const; + bool ValidColRowOrReplicated( SCSIZE & rC, SCSIZE & rR ) const; + void SetErrorAtInterpreter( FormulaError nError ) const; + + void PutDouble(double fVal, SCSIZE nC, SCSIZE nR); + void PutDouble( double fVal, SCSIZE nIndex); + void PutDouble(const double* pArray, size_t nLen, SCSIZE nC, SCSIZE nR); + + void PutString(const svl::SharedString& rStr, SCSIZE nC, SCSIZE nR); + void PutString(const svl::SharedString& rStr, SCSIZE nIndex); + void PutString(const svl::SharedString* pArray, size_t nLen, SCSIZE nC, SCSIZE nR); + + void PutEmpty(SCSIZE nC, SCSIZE nR); + void PutEmptyPath(SCSIZE nC, SCSIZE nR); + void PutError( FormulaError nErrorCode, SCSIZE nC, SCSIZE nR ); + void PutBoolean(bool bVal, SCSIZE nC, SCSIZE nR); + FormulaError GetError( SCSIZE nC, SCSIZE nR) const; + double GetDouble(SCSIZE nC, SCSIZE nR) const; + double GetDouble( SCSIZE nIndex) const; + double GetDoubleWithStringConversion(SCSIZE nC, SCSIZE nR) const; + svl::SharedString GetString(SCSIZE nC, SCSIZE nR) const; + svl::SharedString GetString( SCSIZE nIndex) const; + svl::SharedString GetString( SvNumberFormatter& rFormatter, SCSIZE nC, SCSIZE nR) const; + ScMatrixValue Get(SCSIZE nC, SCSIZE nR) const; + bool IsStringOrEmpty( SCSIZE nIndex ) const; + bool IsStringOrEmpty( SCSIZE nC, SCSIZE nR ) const; + bool IsEmpty( SCSIZE nC, SCSIZE nR ) const; + bool IsEmptyCell( SCSIZE nC, SCSIZE nR ) const; + bool IsEmptyResult( SCSIZE nC, SCSIZE nR ) const; + bool IsEmptyPath( SCSIZE nC, SCSIZE nR ) const; + bool IsValue( SCSIZE nIndex ) const; + bool IsValue( SCSIZE nC, SCSIZE nR ) const; + bool IsValueOrEmpty( SCSIZE nC, SCSIZE nR ) const; + bool IsBoolean( SCSIZE nC, SCSIZE nR ) const; + bool IsNumeric() const; + + void MatCopy(ScMatrixImpl& mRes) const; + void MatTrans(ScMatrixImpl& mRes) const; + void FillDouble( double fVal, SCSIZE nC1, SCSIZE nR1, SCSIZE nC2, SCSIZE nR2 ); + void PutDoubleVector( const ::std::vector< double > & rVec, SCSIZE nC, SCSIZE nR ); + void PutStringVector( const ::std::vector< svl::SharedString > & rVec, SCSIZE nC, SCSIZE nR ); + void PutEmptyVector( SCSIZE nCount, SCSIZE nC, SCSIZE nR ); + void PutEmptyResultVector( SCSIZE nCount, SCSIZE nC, SCSIZE nR ); + void PutEmptyPathVector( SCSIZE nCount, SCSIZE nC, SCSIZE nR ); + void CompareEqual(); + void CompareNotEqual(); + void CompareLess(); + void CompareGreater(); + void CompareLessEqual(); + void CompareGreaterEqual(); + double And() const; + double Or() const; + double Xor() const; + + ScMatrix::KahanIterateResult Sum( bool bTextAsZero, bool bIgnoreErrorValues ) const; + ScMatrix::KahanIterateResult SumSquare( bool bTextAsZero, bool bIgnoreErrorValues ) const; + ScMatrix::DoubleIterateResult Product( bool bTextAsZero, bool bIgnoreErrorValues ) const; + size_t Count(bool bCountStrings, bool bCountErrors, bool bIgnoreEmptyStrings) const; + size_t MatchDoubleInColumns(double fValue, size_t nCol1, size_t nCol2) const; + size_t MatchStringInColumns(const svl::SharedString& rStr, size_t nCol1, size_t nCol2) const; + + double GetMaxValue( bool bTextAsZero, bool bIgnoreErrorValues ) const; + double GetMinValue( bool bTextAsZero, bool bIgnoreErrorValues ) const; + double GetGcd() const; + double GetLcm() const; + + ScMatrixRef CompareMatrix( sc::Compare& rComp, size_t nMatPos, sc::CompareOptions* pOptions ) const; + + void GetDoubleArray( std::vector<double>& rArray, bool bEmptyAsZero ) const; + void MergeDoubleArrayMultiply( std::vector<double>& rArray ) const; + + template<typename T> + void ApplyOperation(T aOp, ScMatrixImpl& rMat); + + void ExecuteOperation(const std::pair<size_t, size_t>& rStartPos, + const std::pair<size_t, size_t>& rEndPos, const ScMatrix::DoubleOpFunction& aDoubleFunc, + const ScMatrix::BoolOpFunction& aBoolFunc, const ScMatrix::StringOpFunction& aStringFunc, + const ScMatrix::EmptyOpFunction& aEmptyFunc) const; + + template<typename T, typename tRes> + ScMatrix::IterateResultMultiple<tRes> ApplyCollectOperation(const std::vector<T>& aOp); + + void MatConcat(SCSIZE nMaxCol, SCSIZE nMaxRow, const ScMatrixRef& xMat1, const ScMatrixRef& xMat2, + SvNumberFormatter& rFormatter, svl::SharedStringPool& rPool); + + void ExecuteBinaryOp(SCSIZE nMaxCol, SCSIZE nMaxRow, const ScMatrix& rInputMat1, const ScMatrix& rInputMat2, + ScInterpreter* pInterpreter, ScMatrix::CalculateOpFunction op); + bool IsValueOrEmpty( const MatrixImplType::const_position_type & rPos ) const; + double GetDouble( const MatrixImplType::const_position_type & rPos) const; + FormulaError GetErrorIfNotString( const MatrixImplType::const_position_type & rPos ) const; + bool IsValue( const MatrixImplType::const_position_type & rPos ) const; + FormulaError GetError(const MatrixImplType::const_position_type & rPos) const; + bool IsStringOrEmpty(const MatrixImplType::const_position_type & rPos) const; + svl::SharedString GetString(const MatrixImplType::const_position_type& rPos) const; + +#if DEBUG_MATRIX + void Dump() const; +#endif + +private: + void CalcPosition(SCSIZE nIndex, SCSIZE& rC, SCSIZE& rR) const; +}; + +static std::once_flag bElementsMaxFetched; +static std::atomic<size_t> nElementsMax; + +/** The maximum number of elements a matrix or the pool may have at runtime. + + @param nMemory + If 0, the arbitrary limit of one matrix is returned. + If >0, the given memory pool divided by the average size of a + matrix element is returned, which is used to initialize + nElementsMax. + */ +static size_t GetElementsMax( size_t nMemory ) +{ + // Arbitrarily assuming 12 bytes per element, 8 bytes double plus + // overhead. Stored as an array in an mdds container it's less, but for + // strings or mixed matrix it can be much more... + constexpr size_t nPerElem = 12; + if (nMemory) + return nMemory / nPerElem; + + // Arbitrarily assuming 1GB memory. Could be dynamic at some point. + constexpr size_t nMemMax = 0x40000000; + // With 1GB that's ~85M elements, or 85 whole columns. + constexpr size_t nElemMax = nMemMax / nPerElem; + // With MAXROWCOUNT==1048576 and 128 columns => 128M elements, 1.5GB + constexpr size_t nArbitraryLimit = size_t(MAXROWCOUNT) * 128; + // With the constant 1GB from above that's the actual value. + return std::min(nElemMax, nArbitraryLimit); +} + +ScMatrixImpl::ScMatrixImpl(SCSIZE nC, SCSIZE nR) : + maMat(nR, nC), maMatFlag(nR, nC), pErrorInterpreter(nullptr) +{ + nElementsMax -= GetElementCount(); +} + +ScMatrixImpl::ScMatrixImpl(SCSIZE nC, SCSIZE nR, double fInitVal) : + maMat(nR, nC, fInitVal), maMatFlag(nR, nC), pErrorInterpreter(nullptr) +{ + nElementsMax -= GetElementCount(); +} + +ScMatrixImpl::ScMatrixImpl( size_t nC, size_t nR, const std::vector<double>& rInitVals ) : + maMat(nR, nC, rInitVals.begin(), rInitVals.end()), maMatFlag(nR, nC), pErrorInterpreter(nullptr) +{ + nElementsMax -= GetElementCount(); +} + +ScMatrixImpl::~ScMatrixImpl() +{ + nElementsMax += GetElementCount(); + suppress_fun_call_w_exception(Clear()); +} + +void ScMatrixImpl::Clear() +{ + suppress_fun_call_w_exception(maMat.clear()); + maMatFlag.clear(); +} + +void ScMatrixImpl::Resize(SCSIZE nC, SCSIZE nR) +{ + nElementsMax += GetElementCount(); + if (ScMatrix::IsSizeAllocatable( nC, nR)) + { + maMat.resize(nR, nC); + maMatFlag.resize(nR, nC); + } + else + { + // Invalid matrix size, allocate 1x1 matrix with error value. + maMat.resize(1, 1, CreateDoubleError( FormulaError::MatrixSize)); + maMatFlag.resize(1, 1); + } + nElementsMax -= GetElementCount(); +} + +void ScMatrixImpl::Resize(SCSIZE nC, SCSIZE nR, double fVal) +{ + nElementsMax += GetElementCount(); + if (ScMatrix::IsSizeAllocatable( nC, nR)) + { + maMat.resize(nR, nC, fVal); + maMatFlag.resize(nR, nC); + } + else + { + // Invalid matrix size, allocate 1x1 matrix with error value. + maMat.resize(1, 1, CreateDoubleError( FormulaError::StackOverflow)); + maMatFlag.resize(1, 1); + } + nElementsMax -= GetElementCount(); +} + +void ScMatrixImpl::SetErrorInterpreter( ScInterpreter* p) +{ + pErrorInterpreter = p; +} + +void ScMatrixImpl::GetDimensions( SCSIZE& rC, SCSIZE& rR) const +{ + MatrixImplType::size_pair_type aSize = maMat.size(); + rR = aSize.row; + rC = aSize.column; +} + +SCSIZE ScMatrixImpl::GetElementCount() const +{ + MatrixImplType::size_pair_type aSize = maMat.size(); + return aSize.row * aSize.column; +} + +bool ScMatrixImpl::ValidColRow( SCSIZE nC, SCSIZE nR) const +{ + MatrixImplType::size_pair_type aSize = maMat.size(); + return nR < aSize.row && nC < aSize.column; +} + +bool ScMatrixImpl::ValidColRowReplicated( SCSIZE & rC, SCSIZE & rR ) const +{ + MatrixImplType::size_pair_type aSize = maMat.size(); + if (aSize.column == 1 && aSize.row == 1) + { + rC = 0; + rR = 0; + return true; + } + else if (aSize.column == 1 && rR < aSize.row) + { + // single column matrix. + rC = 0; + return true; + } + else if (aSize.row == 1 && rC < aSize.column) + { + // single row matrix. + rR = 0; + return true; + } + return false; +} + +bool ScMatrixImpl::ValidColRowOrReplicated( SCSIZE & rC, SCSIZE & rR ) const +{ + return ValidColRow( rC, rR) || ValidColRowReplicated( rC, rR); +} + +void ScMatrixImpl::SetErrorAtInterpreter( FormulaError nError ) const +{ + if ( pErrorInterpreter ) + pErrorInterpreter->SetError( nError); +} + +void ScMatrixImpl::PutDouble(double fVal, SCSIZE nC, SCSIZE nR) +{ + if (ValidColRow( nC, nR)) + maMat.set(nR, nC, fVal); + else + { + OSL_FAIL("ScMatrixImpl::PutDouble: dimension error"); + } +} + +void ScMatrixImpl::PutDouble(const double* pArray, size_t nLen, SCSIZE nC, SCSIZE nR) +{ + if (ValidColRow( nC, nR)) + maMat.set(nR, nC, pArray, pArray + nLen); + else + { + OSL_FAIL("ScMatrixImpl::PutDouble: dimension error"); + } +} + +void ScMatrixImpl::PutDouble( double fVal, SCSIZE nIndex) +{ + SCSIZE nC, nR; + CalcPosition(nIndex, nC, nR); + PutDouble(fVal, nC, nR); +} + +void ScMatrixImpl::PutString(const svl::SharedString& rStr, SCSIZE nC, SCSIZE nR) +{ + if (ValidColRow( nC, nR)) + maMat.set(nR, nC, rStr); + else + { + OSL_FAIL("ScMatrixImpl::PutString: dimension error"); + } +} + +void ScMatrixImpl::PutString(const svl::SharedString* pArray, size_t nLen, SCSIZE nC, SCSIZE nR) +{ + if (ValidColRow( nC, nR)) + maMat.set(nR, nC, pArray, pArray + nLen); + else + { + OSL_FAIL("ScMatrixImpl::PutString: dimension error"); + } +} + +void ScMatrixImpl::PutString(const svl::SharedString& rStr, SCSIZE nIndex) +{ + SCSIZE nC, nR; + CalcPosition(nIndex, nC, nR); + PutString(rStr, nC, nR); +} + +void ScMatrixImpl::PutEmpty(SCSIZE nC, SCSIZE nR) +{ + if (ValidColRow( nC, nR)) + { + maMat.set_empty(nR, nC); + maMatFlag.set_empty(nR, nC); + } + else + { + OSL_FAIL("ScMatrixImpl::PutEmpty: dimension error"); + } +} + +void ScMatrixImpl::PutEmptyPath(SCSIZE nC, SCSIZE nR) +{ + if (ValidColRow( nC, nR)) + { + maMat.set_empty(nR, nC); +#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12 && __cplusplus == 202002L +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" +#endif + maMatFlag.set(nR, nC, SC_MATFLAG_EMPTYPATH); +#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12 && __cplusplus == 202002L +#pragma GCC diagnostic pop +#endif + } + else + { + OSL_FAIL("ScMatrixImpl::PutEmptyPath: dimension error"); + } +} + +void ScMatrixImpl::PutError( FormulaError nErrorCode, SCSIZE nC, SCSIZE nR ) +{ + maMat.set(nR, nC, CreateDoubleError(nErrorCode)); +} + +void ScMatrixImpl::PutBoolean(bool bVal, SCSIZE nC, SCSIZE nR) +{ + if (ValidColRow( nC, nR)) + maMat.set(nR, nC, bVal); + else + { + OSL_FAIL("ScMatrixImpl::PutBoolean: dimension error"); + } +} + +FormulaError ScMatrixImpl::GetError( SCSIZE nC, SCSIZE nR) const +{ + if (ValidColRowOrReplicated( nC, nR )) + { + double fVal = maMat.get_numeric(nR, nC); + return GetDoubleErrorValue(fVal); + } + else + { + OSL_FAIL("ScMatrixImpl::GetError: dimension error"); + return FormulaError::NoValue; + } +} + +double ScMatrixImpl::GetDouble(SCSIZE nC, SCSIZE nR) const +{ + if (ValidColRowOrReplicated( nC, nR )) + { + double fVal = maMat.get_numeric(nR, nC); + if ( pErrorInterpreter ) + { + FormulaError nError = GetDoubleErrorValue(fVal); + if ( nError != FormulaError::NONE ) + SetErrorAtInterpreter( nError); + } + return fVal; + } + else + { + OSL_FAIL("ScMatrixImpl::GetDouble: dimension error"); + return CreateDoubleError( FormulaError::NoValue); + } +} + +double ScMatrixImpl::GetDouble( SCSIZE nIndex) const +{ + SCSIZE nC, nR; + CalcPosition(nIndex, nC, nR); + return GetDouble(nC, nR); +} + +double ScMatrixImpl::GetDoubleWithStringConversion(SCSIZE nC, SCSIZE nR) const +{ + ScMatrixValue aMatVal = Get(nC, nR); + if (aMatVal.nType == ScMatValType::String) + return convertStringToValue( pErrorInterpreter, aMatVal.aStr.getString()); + return aMatVal.fVal; +} + +svl::SharedString ScMatrixImpl::GetString(SCSIZE nC, SCSIZE nR) const +{ + if (ValidColRowOrReplicated( nC, nR )) + { + return GetString(maMat.position(nR, nC)); + } + else + { + OSL_FAIL("ScMatrixImpl::GetString: dimension error"); + } + return svl::SharedString::getEmptyString(); +} + +svl::SharedString ScMatrixImpl::GetString(const MatrixImplType::const_position_type& rPos) const +{ + double fErr = 0.0; + switch (maMat.get_type(rPos)) + { + case mdds::mtm::element_string: + return maMat.get_string(rPos); + case mdds::mtm::element_empty: + return svl::SharedString::getEmptyString(); + case mdds::mtm::element_numeric: + case mdds::mtm::element_boolean: + fErr = maMat.get_numeric(rPos); + [[fallthrough]]; + default: + OSL_FAIL("ScMatrixImpl::GetString: access error, no string"); + } + SetErrorAtInterpreter(GetDoubleErrorValue(fErr)); + return svl::SharedString::getEmptyString(); +} + +svl::SharedString ScMatrixImpl::GetString( SCSIZE nIndex) const +{ + SCSIZE nC, nR; + CalcPosition(nIndex, nC, nR); + return GetString(nC, nR); +} + +svl::SharedString ScMatrixImpl::GetString( SvNumberFormatter& rFormatter, SCSIZE nC, SCSIZE nR) const +{ + if (!ValidColRowOrReplicated( nC, nR )) + { + OSL_FAIL("ScMatrixImpl::GetString: dimension error"); + return svl::SharedString::getEmptyString(); + } + + double fVal = 0.0; + MatrixImplType::const_position_type aPos = maMat.position(nR, nC); + switch (maMat.get_type(aPos)) + { + case mdds::mtm::element_string: + return maMat.get_string(aPos); + case mdds::mtm::element_empty: + { + if (maMatFlag.get<uint8_t>(nR, nC) != SC_MATFLAG_EMPTYPATH) + // not an empty path. + return svl::SharedString::getEmptyString(); + + // result of empty FALSE jump path + sal_uInt32 nKey = rFormatter.GetStandardFormat( SvNumFormatType::LOGICAL, + ScGlobal::eLnge); + OUString aStr; + const Color* pColor = nullptr; + rFormatter.GetOutputString( 0.0, nKey, aStr, &pColor); + return svl::SharedString( aStr); // string not interned + } + case mdds::mtm::element_numeric: + case mdds::mtm::element_boolean: + fVal = maMat.get_numeric(aPos); + break; + default: + ; + } + + FormulaError nError = GetDoubleErrorValue(fVal); + if (nError != FormulaError::NONE) + { + SetErrorAtInterpreter( nError); + return svl::SharedString( ScGlobal::GetErrorString( nError)); // string not interned + } + + sal_uInt32 nKey = rFormatter.GetStandardFormat( SvNumFormatType::NUMBER, + ScGlobal::eLnge); + OUString aStr; + rFormatter.GetInputLineString( fVal, nKey, aStr); + return svl::SharedString( aStr); // string not interned +} + +ScMatrixValue ScMatrixImpl::Get(SCSIZE nC, SCSIZE nR) const +{ + ScMatrixValue aVal; + if (ValidColRowOrReplicated(nC, nR)) + { + MatrixImplType::const_position_type aPos = maMat.position(nR, nC); + mdds::mtm::element_t eType = maMat.get_type(aPos); + switch (eType) + { + case mdds::mtm::element_boolean: + aVal.nType = ScMatValType::Boolean; + aVal.fVal = double(maMat.get_boolean(aPos)); + break; + case mdds::mtm::element_numeric: + aVal.nType = ScMatValType::Value; + aVal.fVal = maMat.get_numeric(aPos); + break; + case mdds::mtm::element_string: + aVal.nType = ScMatValType::String; + aVal.aStr = maMat.get_string(aPos); + break; + case mdds::mtm::element_empty: + /* TODO: do we need to pass the differentiation of 'empty' and + * 'empty result' to the outer world anywhere? */ + switch (maMatFlag.get_type(nR, nC)) + { + case mdds::mtm::element_empty: + aVal.nType = ScMatValType::Empty; + break; + case mdds::mtm::element_integer: + aVal.nType = maMatFlag.get<uint8_t>(nR, nC) + == SC_MATFLAG_EMPTYPATH ? ScMatValType::EmptyPath : ScMatValType::Empty; + break; + default: + assert(false); + } + aVal.fVal = 0.0; + break; + default: + ; + } + } + else + { + OSL_FAIL("ScMatrixImpl::Get: dimension error"); + } + return aVal; +} + +bool ScMatrixImpl::IsStringOrEmpty( SCSIZE nIndex ) const +{ + SCSIZE nC, nR; + CalcPosition(nIndex, nC, nR); + return IsStringOrEmpty(nC, nR); +} + +bool ScMatrixImpl::IsStringOrEmpty( SCSIZE nC, SCSIZE nR ) const +{ + if (!ValidColRowOrReplicated( nC, nR )) + return false; + + switch (maMat.get_type(nR, nC)) + { + case mdds::mtm::element_empty: + case mdds::mtm::element_string: + return true; + default: + ; + } + return false; +} + +bool ScMatrixImpl::IsEmpty( SCSIZE nC, SCSIZE nR ) const +{ + if (!ValidColRowOrReplicated( nC, nR )) + return false; + + // Flag must indicate an 'empty' or 'empty cell' or 'empty result' element, + // but not an 'empty path' element. + return maMat.get_type(nR, nC) == mdds::mtm::element_empty && + maMatFlag.get_integer(nR, nC) != SC_MATFLAG_EMPTYPATH; +} + +bool ScMatrixImpl::IsEmptyCell( SCSIZE nC, SCSIZE nR ) const +{ + if (!ValidColRowOrReplicated( nC, nR )) + return false; + + // Flag must indicate an 'empty cell' element instead of an + // 'empty' or 'empty result' or 'empty path' element. + return maMat.get_type(nR, nC) == mdds::mtm::element_empty && + maMatFlag.get_type(nR, nC) == mdds::mtm::element_empty; +} + +bool ScMatrixImpl::IsEmptyResult( SCSIZE nC, SCSIZE nR ) const +{ + if (!ValidColRowOrReplicated( nC, nR )) + return false; + + // Flag must indicate an 'empty result' element instead of an + // 'empty' or 'empty cell' or 'empty path' element. + return maMat.get_type(nR, nC) == mdds::mtm::element_empty && + maMatFlag.get_integer(nR, nC) == SC_MATFLAG_EMPTYRESULT; +} + +bool ScMatrixImpl::IsEmptyPath( SCSIZE nC, SCSIZE nR ) const +{ + // Flag must indicate an 'empty path' element. + if (ValidColRowOrReplicated( nC, nR )) + return maMat.get_type(nR, nC) == mdds::mtm::element_empty && + maMatFlag.get_integer(nR, nC) == SC_MATFLAG_EMPTYPATH; + else + return true; +} + +bool ScMatrixImpl::IsValue( SCSIZE nIndex ) const +{ + SCSIZE nC, nR; + CalcPosition(nIndex, nC, nR); + return IsValue(nC, nR); +} + +bool ScMatrixImpl::IsValue( SCSIZE nC, SCSIZE nR ) const +{ + if (!ValidColRowOrReplicated( nC, nR )) + return false; + + switch (maMat.get_type(nR, nC)) + { + case mdds::mtm::element_boolean: + case mdds::mtm::element_numeric: + return true; + default: + ; + } + return false; +} + +bool ScMatrixImpl::IsValueOrEmpty( SCSIZE nC, SCSIZE nR ) const +{ + if (!ValidColRowOrReplicated( nC, nR )) + return false; + + switch (maMat.get_type(nR, nC)) + { + case mdds::mtm::element_boolean: + case mdds::mtm::element_numeric: + case mdds::mtm::element_empty: + return true; + default: + ; + } + return false; +} + +bool ScMatrixImpl::IsBoolean( SCSIZE nC, SCSIZE nR ) const +{ + if (!ValidColRowOrReplicated( nC, nR )) + return false; + + return maMat.get_type(nR, nC) == mdds::mtm::element_boolean; +} + +bool ScMatrixImpl::IsNumeric() const +{ + return maMat.numeric(); +} + +void ScMatrixImpl::MatCopy(ScMatrixImpl& mRes) const +{ + if (maMat.size().row > mRes.maMat.size().row || maMat.size().column > mRes.maMat.size().column) + { + // destination matrix is not large enough. + OSL_FAIL("ScMatrixImpl::MatCopy: dimension error"); + return; + } + + mRes.maMat.copy(maMat); +} + +void ScMatrixImpl::MatTrans(ScMatrixImpl& mRes) const +{ + mRes.maMat = maMat; + mRes.maMat.transpose(); +} + +void ScMatrixImpl::FillDouble( double fVal, SCSIZE nC1, SCSIZE nR1, SCSIZE nC2, SCSIZE nR2 ) +{ + if (ValidColRow( nC1, nR1) && ValidColRow( nC2, nR2)) + { + for (SCSIZE j = nC1; j <= nC2; ++j) + { + // Passing value array is much faster. + std::vector<double> aVals(nR2-nR1+1, fVal); + maMat.set(nR1, j, aVals.begin(), aVals.end()); + } + } + else + { + OSL_FAIL("ScMatrixImpl::FillDouble: dimension error"); + } +} + +void ScMatrixImpl::PutDoubleVector( const ::std::vector< double > & rVec, SCSIZE nC, SCSIZE nR ) +{ + if (!rVec.empty() && ValidColRow( nC, nR) && ValidColRow( nC, nR + rVec.size() - 1)) + { + maMat.set(nR, nC, rVec.begin(), rVec.end()); + } + else + { + OSL_FAIL("ScMatrixImpl::PutDoubleVector: dimension error"); + } +} + +void ScMatrixImpl::PutStringVector( const ::std::vector< svl::SharedString > & rVec, SCSIZE nC, SCSIZE nR ) +{ + if (!rVec.empty() && ValidColRow( nC, nR) && ValidColRow( nC, nR + rVec.size() - 1)) + { + maMat.set(nR, nC, rVec.begin(), rVec.end()); + } + else + { + OSL_FAIL("ScMatrixImpl::PutStringVector: dimension error"); + } +} + +void ScMatrixImpl::PutEmptyVector( SCSIZE nCount, SCSIZE nC, SCSIZE nR ) +{ + if (nCount && ValidColRow( nC, nR) && ValidColRow( nC, nR + nCount - 1)) + { + maMat.set_empty(nR, nC, nCount); + // Flag to indicate that this is 'empty', not 'empty result' or 'empty path'. + maMatFlag.set_empty(nR, nC, nCount); + } + else + { + OSL_FAIL("ScMatrixImpl::PutEmptyVector: dimension error"); + } +} + +void ScMatrixImpl::PutEmptyResultVector( SCSIZE nCount, SCSIZE nC, SCSIZE nR ) +{ + if (nCount && ValidColRow( nC, nR) && ValidColRow( nC, nR + nCount - 1)) + { + maMat.set_empty(nR, nC, nCount); + // Flag to indicate that this is 'empty result', not 'empty' or 'empty path'. + std::vector<uint8_t> aVals(nCount, SC_MATFLAG_EMPTYRESULT); + maMatFlag.set(nR, nC, aVals.begin(), aVals.end()); + } + else + { + OSL_FAIL("ScMatrixImpl::PutEmptyResultVector: dimension error"); + } +} + +void ScMatrixImpl::PutEmptyPathVector( SCSIZE nCount, SCSIZE nC, SCSIZE nR ) +{ + if (nCount && ValidColRow( nC, nR) && ValidColRow( nC, nR + nCount - 1)) + { + maMat.set_empty(nR, nC, nCount); + // Flag to indicate 'empty path'. + std::vector<uint8_t> aVals(nCount, SC_MATFLAG_EMPTYPATH); + maMatFlag.set(nR, nC, aVals.begin(), aVals.end()); + } + else + { + OSL_FAIL("ScMatrixImpl::PutEmptyPathVector: dimension error"); + } +} + +void ScMatrixImpl::CompareEqual() +{ + MatrixImplType::size_pair_type aSize = maMat.size(); + CompareMatrixElemFunc<ElemEqualZero> aFunc(aSize.row, aSize.column); + aFunc = maMat.walk(std::move(aFunc)); + aFunc.swap(maMat); +} + +void ScMatrixImpl::CompareNotEqual() +{ + MatrixImplType::size_pair_type aSize = maMat.size(); + CompareMatrixElemFunc<ElemNotEqualZero> aFunc(aSize.row, aSize.column); + aFunc = maMat.walk(std::move(aFunc)); + aFunc.swap(maMat); +} + +void ScMatrixImpl::CompareLess() +{ + MatrixImplType::size_pair_type aSize = maMat.size(); + CompareMatrixElemFunc<ElemLessZero> aFunc(aSize.row, aSize.column); + aFunc = maMat.walk(std::move(aFunc)); + aFunc.swap(maMat); +} + +void ScMatrixImpl::CompareGreater() +{ + MatrixImplType::size_pair_type aSize = maMat.size(); + CompareMatrixElemFunc<ElemGreaterZero> aFunc(aSize.row, aSize.column); + aFunc = maMat.walk(std::move(aFunc)); + aFunc.swap(maMat); +} + +void ScMatrixImpl::CompareLessEqual() +{ + MatrixImplType::size_pair_type aSize = maMat.size(); + CompareMatrixElemFunc<ElemLessEqualZero> aFunc(aSize.row, aSize.column); + aFunc = maMat.walk(std::move(aFunc)); + aFunc.swap(maMat); +} + +void ScMatrixImpl::CompareGreaterEqual() +{ + MatrixImplType::size_pair_type aSize = maMat.size(); + CompareMatrixElemFunc<ElemGreaterEqualZero> aFunc(aSize.row, aSize.column); + aFunc = maMat.walk(std::move(aFunc)); + aFunc.swap(maMat); +} + +namespace { + +struct AndEvaluator +{ + bool mbResult; + void operate(double fVal) { mbResult &= (fVal != 0.0); } + bool result() const { return mbResult; } + AndEvaluator() : mbResult(true) {} +}; + +struct OrEvaluator +{ + bool mbResult; + void operate(double fVal) { mbResult |= (fVal != 0.0); } + bool result() const { return mbResult; } + OrEvaluator() : mbResult(false) {} +}; + +struct XorEvaluator +{ + bool mbResult; + void operate(double fVal) { mbResult ^= (fVal != 0.0); } + bool result() const { return mbResult; } + XorEvaluator() : mbResult(false) {} +}; + +// Do not short circuit logical operations, in case there are error values +// these need to be propagated even if the result was determined earlier. +template <typename Evaluator> +double EvalMatrix(const MatrixImplType& rMat) +{ + Evaluator aEval; + size_t nRows = rMat.size().row, nCols = rMat.size().column; + for (size_t i = 0; i < nRows; ++i) + { + for (size_t j = 0; j < nCols; ++j) + { + MatrixImplType::const_position_type aPos = rMat.position(i, j); + mdds::mtm::element_t eType = rMat.get_type(aPos); + if (eType != mdds::mtm::element_numeric && eType != mdds::mtm::element_boolean) + // assuming a CompareMat this is an error + return CreateDoubleError(FormulaError::IllegalArgument); + + double fVal = rMat.get_numeric(aPos); + if (!std::isfinite(fVal)) + // DoubleError + return fVal; + + aEval.operate(fVal); + } + } + return aEval.result(); +} + +} + +double ScMatrixImpl::And() const +{ + // All elements must be of value type. + // True only if all the elements have non-zero values. + return EvalMatrix<AndEvaluator>(maMat); +} + +double ScMatrixImpl::Or() const +{ + // All elements must be of value type. + // True if at least one element has a non-zero value. + return EvalMatrix<OrEvaluator>(maMat); +} + +double ScMatrixImpl::Xor() const +{ + // All elements must be of value type. + // True if an odd number of elements have a non-zero value. + return EvalMatrix<XorEvaluator>(maMat); +} + +namespace { + +template<typename Op, typename tRes> +class WalkElementBlocks +{ + Op maOp; + ScMatrix::IterateResult<tRes> maRes; + bool mbTextAsZero:1; + bool mbIgnoreErrorValues:1; +public: + WalkElementBlocks(bool bTextAsZero, bool bIgnoreErrorValues) : + maRes(Op::InitVal, 0), + mbTextAsZero(bTextAsZero), mbIgnoreErrorValues(bIgnoreErrorValues) + {} + + const ScMatrix::IterateResult<tRes>& getResult() const { return maRes; } + + void operator() (const MatrixImplType::element_block_node_type& node) + { + switch (node.type) + { + case mdds::mtm::element_numeric: + { + typedef MatrixImplType::numeric_block_type block_type; + + size_t nIgnored = 0; + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + for (; it != itEnd; ++it) + { + if (mbIgnoreErrorValues && !std::isfinite(*it)) + { + ++nIgnored; + continue; + } + maOp(maRes.maAccumulator, *it); + } + maRes.mnCount += node.size - nIgnored; + } + break; + case mdds::mtm::element_boolean: + { + typedef MatrixImplType::boolean_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + for (; it != itEnd; ++it) + { + maOp(maRes.maAccumulator, *it); + } + maRes.mnCount += node.size; + } + break; + case mdds::mtm::element_string: + if (mbTextAsZero) + maRes.mnCount += node.size; + break; + case mdds::mtm::element_empty: + default: + ; + } + } +}; + +template<typename Op, typename tRes> +class WalkElementBlocksMultipleValues +{ + const std::vector<Op>* mpOp; + ScMatrix::IterateResultMultiple<tRes> maRes; +public: + WalkElementBlocksMultipleValues(const std::vector<Op>& aOp) : + mpOp(&aOp), maRes(0) + { + for (const auto& rpOp : *mpOp) + maRes.maAccumulator.emplace_back(rpOp.mInitVal); + } + + WalkElementBlocksMultipleValues( const WalkElementBlocksMultipleValues& ) = delete; + WalkElementBlocksMultipleValues& operator= ( const WalkElementBlocksMultipleValues& ) = delete; + + WalkElementBlocksMultipleValues(WalkElementBlocksMultipleValues&& r) noexcept + : mpOp(r.mpOp), maRes(r.maRes.mnCount) + { + maRes.maAccumulator = std::move(r.maRes.maAccumulator); + } + + WalkElementBlocksMultipleValues& operator=(WalkElementBlocksMultipleValues&& r) noexcept + { + mpOp = r.mpOp; + maRes.maAccumulator = std::move(r.maRes.maAccumulator); + maRes.mnCount = r.maRes.mnCount; + return *this; + } + + const ScMatrix::IterateResultMultiple<tRes>& getResult() const { return maRes; } + + void operator() (const MatrixImplType::element_block_node_type& node) + { + switch (node.type) + { + case mdds::mtm::element_numeric: + { + typedef MatrixImplType::numeric_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + for (; it != itEnd; ++it) + { + for (size_t i = 0u; i < mpOp->size(); ++i) + (*mpOp)[i](maRes.maAccumulator[i], *it); + } + maRes.mnCount += node.size; + } + break; + case mdds::mtm::element_boolean: + { + typedef MatrixImplType::boolean_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + for (; it != itEnd; ++it) + { + for (size_t i = 0u; i < mpOp->size(); ++i) + (*mpOp)[i](maRes.maAccumulator[i], *it); + } + maRes.mnCount += node.size; + } + break; + case mdds::mtm::element_string: + case mdds::mtm::element_empty: + default: + ; + } + } +}; + +class CountElements +{ + size_t mnCount; + bool mbCountString; + bool mbCountErrors; + bool mbIgnoreEmptyStrings; +public: + explicit CountElements(bool bCountString, bool bCountErrors, bool bIgnoreEmptyStrings) : + mnCount(0), mbCountString(bCountString), mbCountErrors(bCountErrors), + mbIgnoreEmptyStrings(bIgnoreEmptyStrings) {} + + size_t getCount() const { return mnCount; } + + void operator() (const MatrixImplType::element_block_node_type& node) + { + switch (node.type) + { + case mdds::mtm::element_numeric: + mnCount += node.size; + if (!mbCountErrors) + { + typedef MatrixImplType::numeric_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + for (; it != itEnd; ++it) + { + if (!std::isfinite(*it)) + --mnCount; + } + } + break; + case mdds::mtm::element_boolean: + mnCount += node.size; + break; + case mdds::mtm::element_string: + if (mbCountString) + { + mnCount += node.size; + if (mbIgnoreEmptyStrings) + { + typedef MatrixImplType::string_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + for (; it != itEnd; ++it) + { + if (it->isEmpty()) + --mnCount; + } + } + } + break; + case mdds::mtm::element_empty: + default: + ; + } + } +}; + +const size_t ResultNotSet = std::numeric_limits<size_t>::max(); + +template<typename Type> +class WalkAndMatchElements +{ + Type maMatchValue; + size_t mnStartIndex; + size_t mnStopIndex; + size_t mnResult; + size_t mnIndex; + +public: + WalkAndMatchElements(Type aMatchValue, const MatrixImplType::size_pair_type& aSize, size_t nCol1, size_t nCol2) : + maMatchValue(std::move(aMatchValue)), + mnStartIndex( nCol1 * aSize.row ), + mnStopIndex( (nCol2 + 1) * aSize.row ), + mnResult(ResultNotSet), + mnIndex(0) + { + assert( nCol1 < aSize.column && nCol2 < aSize.column); + } + + size_t getMatching() const { return mnResult; } + + size_t getRemainingCount() const + { + return mnIndex < mnStopIndex ? mnStopIndex - mnIndex : 0; + } + + size_t compare(const MatrixImplType::element_block_node_type& node) const; + + void operator() (const MatrixImplType::element_block_node_type& node) + { + // early exit if match already found + if (mnResult != ResultNotSet) + return; + + // limit lookup to the requested columns + if (mnStartIndex <= mnIndex && getRemainingCount() > 0) + { + mnResult = compare(node); + } + + mnIndex += node.size; + } +}; + +template<> +size_t WalkAndMatchElements<double>::compare(const MatrixImplType::element_block_node_type& node) const +{ + size_t nCount = 0; + switch (node.type) + { + case mdds::mtm::element_numeric: + { + typedef MatrixImplType::numeric_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + const size_t nRemaining = getRemainingCount(); + for (; it != itEnd && nCount < nRemaining; ++it, ++nCount) + { + if (*it == maMatchValue) + { + return mnIndex + nCount; + } + } + break; + } + case mdds::mtm::element_boolean: + { + typedef MatrixImplType::boolean_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + const size_t nRemaining = getRemainingCount(); + for (; it != itEnd && nCount < nRemaining; ++it, ++nCount) + { + if (int(*it) == maMatchValue) + { + return mnIndex + nCount; + } + } + break; + } + break; + case mdds::mtm::element_string: + case mdds::mtm::element_empty: + default: + ; + } + return ResultNotSet; +} + +template<> +size_t WalkAndMatchElements<svl::SharedString>::compare(const MatrixImplType::element_block_node_type& node) const +{ + switch (node.type) + { + case mdds::mtm::element_string: + { + size_t nCount = 0; + typedef MatrixImplType::string_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + const size_t nRemaining = getRemainingCount(); + for (; it != itEnd && nCount < nRemaining; ++it, ++nCount) + { + if (it->getDataIgnoreCase() == maMatchValue.getDataIgnoreCase()) + { + return mnIndex + nCount; + } + } + break; + } + case mdds::mtm::element_boolean: + case mdds::mtm::element_numeric: + case mdds::mtm::element_empty: + default: + ; + } + return ResultNotSet; +} + +struct MaxOp +{ + static double init() { return -std::numeric_limits<double>::max(); } + static double compare(double left, double right) + { + if (!std::isfinite(left)) + return left; + if (!std::isfinite(right)) + return right; + return std::max(left, right); + } + + static double boolValue( + MatrixImplType::boolean_block_type::const_iterator it, + const MatrixImplType::boolean_block_type::const_iterator& itEnd) + { + // If the array has at least one true value, the maximum value is 1. + it = std::find(it, itEnd, true); + return it == itEnd ? 0.0 : 1.0; + } +}; + +struct MinOp +{ + static double init() { return std::numeric_limits<double>::max(); } + static double compare(double left, double right) + { + if (!std::isfinite(left)) + return left; + if (!std::isfinite(right)) + return right; + return std::min(left, right); + } + + static double boolValue( + MatrixImplType::boolean_block_type::const_iterator it, + const MatrixImplType::boolean_block_type::const_iterator& itEnd) + { + // If the array has at least one false value, the minimum value is 0. + it = std::find(it, itEnd, false); + return it == itEnd ? 1.0 : 0.0; + } +}; + +struct Lcm +{ + static double init() { return 1.0; } + static double calculate(double fx,double fy) + { + return (fx*fy)/ScInterpreter::ScGetGCD(fx,fy); + } + + static double boolValue( + MatrixImplType::boolean_block_type::const_iterator it, + const MatrixImplType::boolean_block_type::const_iterator& itEnd) + { + // If the array has at least one false value, the minimum value is 0. + it = std::find(it, itEnd, false); + return it == itEnd ? 1.0 : 0.0; + } +}; + +struct Gcd +{ + static double init() { return 0.0; } + static double calculate(double fx,double fy) + { + return ScInterpreter::ScGetGCD(fx,fy); + } + + static double boolValue( + MatrixImplType::boolean_block_type::const_iterator it, + const MatrixImplType::boolean_block_type::const_iterator& itEnd) + { + // If the array has at least one true value, the gcdResult is 1. + it = std::find(it, itEnd, true); + return it == itEnd ? 0.0 : 1.0; + } +}; + +template<typename Op> +class CalcMaxMinValue +{ + double mfVal; + bool mbTextAsZero; + bool mbIgnoreErrorValues; + bool mbHasValue; +public: + CalcMaxMinValue( bool bTextAsZero, bool bIgnoreErrorValues ) : + mfVal(Op::init()), + mbTextAsZero(bTextAsZero), + mbIgnoreErrorValues(bIgnoreErrorValues), + mbHasValue(false) {} + + double getValue() const { return mbHasValue ? mfVal : 0.0; } + + void operator() (const MatrixImplType::element_block_node_type& node) + { + + switch (node.type) + { + case mdds::mtm::element_numeric: + { + typedef MatrixImplType::numeric_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + if (mbIgnoreErrorValues) + { + for (; it != itEnd; ++it) + { + if (std::isfinite(*it)) + mfVal = Op::compare(mfVal, *it); + } + } + else + { + for (; it != itEnd; ++it) + mfVal = Op::compare(mfVal, *it); + } + + mbHasValue = true; + } + break; + case mdds::mtm::element_boolean: + { + typedef MatrixImplType::boolean_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + double fVal = Op::boolValue(it, itEnd); + mfVal = Op::compare(mfVal, fVal); + mbHasValue = true; + } + break; + case mdds::mtm::element_string: + case mdds::mtm::element_empty: + { + // empty elements are treated as empty strings. + if (mbTextAsZero) + { + mfVal = Op::compare(mfVal, 0.0); + mbHasValue = true; + } + } + break; + default: + ; + } + } +}; + +template<typename Op> +class CalcGcdLcm +{ + double mfval; + +public: + CalcGcdLcm() : mfval(Op::init()) {} + + double getResult() const { return mfval; } + + void operator() ( const MatrixImplType::element_block_node_type& node ) + { + switch (node.type) + { + case mdds::mtm::element_numeric: + { + typedef MatrixImplType::numeric_block_type block_type; + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + + for ( ; it != itEnd; ++it) + { + if (*it < 0.0) + mfval = CreateDoubleError(FormulaError::IllegalArgument); + else + mfval = ::rtl::math::approxFloor( Op::calculate(*it,mfval)); + } + } + break; + case mdds::mtm::element_boolean: + { + typedef MatrixImplType::boolean_block_type block_type; + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + + mfval = Op::boolValue(it, itEnd); + } + break; + case mdds::mtm::element_empty: + case mdds::mtm::element_string: + { + mfval = CreateDoubleError(FormulaError::IllegalArgument); + } + break; + default: + ; + } + } +}; + +double evaluate( double fVal, ScQueryOp eOp ) +{ + if (!std::isfinite(fVal)) + return fVal; + + switch (eOp) + { + case SC_EQUAL: + return fVal == 0.0 ? 1.0 : 0.0; + case SC_LESS: + return fVal < 0.0 ? 1.0 : 0.0; + case SC_GREATER: + return fVal > 0.0 ? 1.0 : 0.0; + case SC_LESS_EQUAL: + return fVal <= 0.0 ? 1.0 : 0.0; + case SC_GREATER_EQUAL: + return fVal >= 0.0 ? 1.0 : 0.0; + case SC_NOT_EQUAL: + return fVal != 0.0 ? 1.0 : 0.0; + default: + ; + } + + SAL_WARN("sc.core", "evaluate: unhandled comparison operator: " << static_cast<int>(eOp)); + return CreateDoubleError( FormulaError::UnknownState); +} + +class CompareMatrixFunc +{ + sc::Compare& mrComp; + size_t mnMatPos; + sc::CompareOptions* mpOptions; + std::vector<double> maResValues; // double instead of bool to transport error values + + void compare() + { + double fVal = sc::CompareFunc( mrComp, mpOptions); + maResValues.push_back(evaluate(fVal, mrComp.meOp)); + } + +public: + CompareMatrixFunc( size_t nResSize, sc::Compare& rComp, size_t nMatPos, sc::CompareOptions* pOptions ) : + mrComp(rComp), mnMatPos(nMatPos), mpOptions(pOptions) + { + maResValues.reserve(nResSize); + } + + CompareMatrixFunc( const CompareMatrixFunc& ) = delete; + CompareMatrixFunc& operator= ( const CompareMatrixFunc& ) = delete; + + CompareMatrixFunc(CompareMatrixFunc&& r) noexcept : + mrComp(r.mrComp), + mnMatPos(r.mnMatPos), + mpOptions(r.mpOptions), + maResValues(std::move(r.maResValues)) {} + + CompareMatrixFunc& operator=(CompareMatrixFunc&& r) noexcept + { + mrComp = r.mrComp; + mnMatPos = r.mnMatPos; + mpOptions = r.mpOptions; + maResValues = std::move(r.maResValues); + return *this; + } + + void operator() (const MatrixImplType::element_block_node_type& node) + { + sc::Compare::Cell& rCell = mrComp.maCells[mnMatPos]; + + switch (node.type) + { + case mdds::mtm::element_numeric: + { + typedef MatrixImplType::numeric_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + for (; it != itEnd; ++it) + { + rCell.mbValue = true; + rCell.mbEmpty = false; + rCell.mfValue = *it; + compare(); + } + } + break; + case mdds::mtm::element_boolean: + { + typedef MatrixImplType::boolean_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + for (; it != itEnd; ++it) + { + rCell.mbValue = true; + rCell.mbEmpty = false; + rCell.mfValue = double(*it); + compare(); + } + } + break; + case mdds::mtm::element_string: + { + typedef MatrixImplType::string_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + for (; it != itEnd; ++it) + { + const svl::SharedString& rStr = *it; + rCell.mbValue = false; + rCell.mbEmpty = false; + rCell.maStr = rStr; + compare(); + } + } + break; + case mdds::mtm::element_empty: + { + rCell.mbValue = false; + rCell.mbEmpty = true; + rCell.maStr = svl::SharedString::getEmptyString(); + for (size_t i = 0; i < node.size; ++i) + compare(); + } + break; + default: + ; + } + } + + const std::vector<double>& getValues() const + { + return maResValues; + } +}; + +/** + * Left-hand side is a matrix while the right-hand side is a numeric value. + */ +class CompareMatrixToNumericFunc +{ + sc::Compare& mrComp; + double mfRightValue; + sc::CompareOptions* mpOptions; + std::vector<double> maResValues; // double instead of bool to transport error values + + void compare() + { + double fVal = sc::CompareFunc(mrComp.maCells[0], mfRightValue, mpOptions); + maResValues.push_back(evaluate(fVal, mrComp.meOp)); + } + + void compareLeftNumeric( double fLeftVal ) + { + double fVal = sc::CompareFunc(fLeftVal, mfRightValue); + maResValues.push_back(evaluate(fVal, mrComp.meOp)); + } + + void compareLeftEmpty( size_t nSize ) + { + double fVal = sc::CompareEmptyToNumericFunc(mfRightValue); + bool bRes = evaluate(fVal, mrComp.meOp); + maResValues.resize(maResValues.size() + nSize, bRes ? 1.0 : 0.0); + } + +public: + CompareMatrixToNumericFunc( size_t nResSize, sc::Compare& rComp, double fRightValue, sc::CompareOptions* pOptions ) : + mrComp(rComp), mfRightValue(fRightValue), mpOptions(pOptions) + { + maResValues.reserve(nResSize); + } + + CompareMatrixToNumericFunc( const CompareMatrixToNumericFunc& ) = delete; + CompareMatrixToNumericFunc& operator= ( const CompareMatrixToNumericFunc& ) = delete; + + CompareMatrixToNumericFunc(CompareMatrixToNumericFunc&& r) noexcept : + mrComp(r.mrComp), + mfRightValue(r.mfRightValue), + mpOptions(r.mpOptions), + maResValues(std::move(r.maResValues)) {} + + CompareMatrixToNumericFunc& operator=(CompareMatrixToNumericFunc&& r) noexcept + { + mrComp = r.mrComp; + mfRightValue = r.mfRightValue; + mpOptions = r.mpOptions; + maResValues = std::move(r.maResValues); + return *this; + } + + void operator() (const MatrixImplType::element_block_node_type& node) + { + switch (node.type) + { + case mdds::mtm::element_numeric: + { + typedef MatrixImplType::numeric_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + for (; it != itEnd; ++it) + compareLeftNumeric(*it); + } + break; + case mdds::mtm::element_boolean: + { + typedef MatrixImplType::boolean_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + for (; it != itEnd; ++it) + compareLeftNumeric(double(*it)); + } + break; + case mdds::mtm::element_string: + { + typedef MatrixImplType::string_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + for (; it != itEnd; ++it) + { + const svl::SharedString& rStr = *it; + sc::Compare::Cell& rCell = mrComp.maCells[0]; + rCell.mbValue = false; + rCell.mbEmpty = false; + rCell.maStr = rStr; + compare(); + } + } + break; + case mdds::mtm::element_empty: + compareLeftEmpty(node.size); + break; + default: + ; + } + } + + const std::vector<double>& getValues() const + { + return maResValues; + } +}; + +class ToDoubleArray +{ + std::vector<double> maArray; + std::vector<double>::iterator miPos; + double mfNaN; + bool mbEmptyAsZero; + + void moveArray( ToDoubleArray& r ) + { + // Re-create the iterator from the new array after the array has been + // moved, to ensure that the iterator points to a valid array + // position. + size_t n = std::distance(r.maArray.begin(), r.miPos); + maArray = std::move(r.maArray); + miPos = maArray.begin(); + std::advance(miPos, n); + } + +public: + ToDoubleArray( size_t nSize, bool bEmptyAsZero ) : + maArray(nSize, 0.0), miPos(maArray.begin()), mbEmptyAsZero(bEmptyAsZero) + { + mfNaN = CreateDoubleError( FormulaError::ElementNaN); + } + + ToDoubleArray( const ToDoubleArray& ) = delete; + ToDoubleArray& operator= ( const ToDoubleArray& ) = delete; + + ToDoubleArray(ToDoubleArray&& r) noexcept : + mfNaN(r.mfNaN), mbEmptyAsZero(r.mbEmptyAsZero) + { + moveArray(r); + } + + ToDoubleArray& operator=(ToDoubleArray&& r) noexcept + { + mfNaN = r.mfNaN; + mbEmptyAsZero = r.mbEmptyAsZero; + moveArray(r); + return *this; + } + + void operator() (const MatrixImplType::element_block_node_type& node) + { + using namespace mdds::mtv; + + switch (node.type) + { + case mdds::mtm::element_numeric: + { + double_element_block::const_iterator it = double_element_block::begin(*node.data); + double_element_block::const_iterator itEnd = double_element_block::end(*node.data); + for (; it != itEnd; ++it, ++miPos) + *miPos = *it; + } + break; + case mdds::mtm::element_boolean: + { + boolean_element_block::const_iterator it = boolean_element_block::begin(*node.data); + boolean_element_block::const_iterator itEnd = boolean_element_block::end(*node.data); + for (; it != itEnd; ++it, ++miPos) + *miPos = *it ? 1.0 : 0.0; + } + break; + case mdds::mtm::element_string: + { + for (size_t i = 0; i < node.size; ++i, ++miPos) + *miPos = mfNaN; + } + break; + case mdds::mtm::element_empty: + { + if (mbEmptyAsZero) + { + std::advance(miPos, node.size); + return; + } + + for (size_t i = 0; i < node.size; ++i, ++miPos) + *miPos = mfNaN; + } + break; + default: + ; + } + } + + void swap(std::vector<double>& rOther) + { + maArray.swap(rOther); + } +}; + +struct ArrayMul +{ + double operator() (const double& lhs, const double& rhs) const + { + return lhs * rhs; + } +}; + +template<typename Op> +class MergeDoubleArrayFunc +{ + std::vector<double>::iterator miPos; + double mfNaN; +public: + MergeDoubleArrayFunc(std::vector<double>& rArray) : miPos(rArray.begin()) + { + mfNaN = CreateDoubleError( FormulaError::ElementNaN); + } + + MergeDoubleArrayFunc( const MergeDoubleArrayFunc& ) = delete; + MergeDoubleArrayFunc& operator= ( const MergeDoubleArrayFunc& ) = delete; + + MergeDoubleArrayFunc( MergeDoubleArrayFunc&& ) = default; + MergeDoubleArrayFunc& operator= ( MergeDoubleArrayFunc&& ) = default; + + void operator() (const MatrixImplType::element_block_node_type& node) + { + using namespace mdds::mtv; + static const Op op; + + switch (node.type) + { + case mdds::mtm::element_numeric: + { + double_element_block::const_iterator it = double_element_block::begin(*node.data); + double_element_block::const_iterator itEnd = double_element_block::end(*node.data); + for (; it != itEnd; ++it, ++miPos) + { + if (GetDoubleErrorValue(*miPos) == FormulaError::ElementNaN) + continue; + + *miPos = op(*miPos, *it); + } + } + break; + case mdds::mtm::element_boolean: + { + boolean_element_block::const_iterator it = boolean_element_block::begin(*node.data); + boolean_element_block::const_iterator itEnd = boolean_element_block::end(*node.data); + for (; it != itEnd; ++it, ++miPos) + { + if (GetDoubleErrorValue(*miPos) == FormulaError::ElementNaN) + continue; + + *miPos = op(*miPos, *it ? 1.0 : 0.0); + } + } + break; + case mdds::mtm::element_string: + { + for (size_t i = 0; i < node.size; ++i, ++miPos) + *miPos = mfNaN; + } + break; + case mdds::mtm::element_empty: + { + // Empty element is equivalent of having a numeric value of 0.0. + for (size_t i = 0; i < node.size; ++i, ++miPos) + { + if (GetDoubleErrorValue(*miPos) == FormulaError::ElementNaN) + continue; + + *miPos = op(*miPos, 0.0); + } + } + break; + default: + ; + } + } +}; + +} + +namespace { + +template<typename TOp, typename tRes> +ScMatrix::IterateResult<tRes> GetValueWithCount(bool bTextAsZero, bool bIgnoreErrorValues, const MatrixImplType& maMat) +{ + WalkElementBlocks<TOp, tRes> aFunc(bTextAsZero, bIgnoreErrorValues); + aFunc = maMat.walk(aFunc); + return aFunc.getResult(); +} + +} + +ScMatrix::KahanIterateResult ScMatrixImpl::Sum(bool bTextAsZero, bool bIgnoreErrorValues) const +{ + return GetValueWithCount<sc::op::Sum, KahanSum>(bTextAsZero, bIgnoreErrorValues, maMat); +} + +ScMatrix::KahanIterateResult ScMatrixImpl::SumSquare(bool bTextAsZero, bool bIgnoreErrorValues) const +{ + return GetValueWithCount<sc::op::SumSquare, KahanSum>(bTextAsZero, bIgnoreErrorValues, maMat); +} + +ScMatrix::DoubleIterateResult ScMatrixImpl::Product(bool bTextAsZero, bool bIgnoreErrorValues) const +{ + return GetValueWithCount<sc::op::Product, double>(bTextAsZero, bIgnoreErrorValues, maMat); +} + +size_t ScMatrixImpl::Count(bool bCountStrings, bool bCountErrors, bool bIgnoreEmptyStrings) const +{ + CountElements aFunc(bCountStrings, bCountErrors, bIgnoreEmptyStrings); + aFunc = maMat.walk(aFunc); + return aFunc.getCount(); +} + +size_t ScMatrixImpl::MatchDoubleInColumns(double fValue, size_t nCol1, size_t nCol2) const +{ + WalkAndMatchElements<double> aFunc(fValue, maMat.size(), nCol1, nCol2); + aFunc = maMat.walk(aFunc); + return aFunc.getMatching(); +} + +size_t ScMatrixImpl::MatchStringInColumns(const svl::SharedString& rStr, size_t nCol1, size_t nCol2) const +{ + WalkAndMatchElements<svl::SharedString> aFunc(rStr, maMat.size(), nCol1, nCol2); + aFunc = maMat.walk(aFunc); + return aFunc.getMatching(); +} + +double ScMatrixImpl::GetMaxValue( bool bTextAsZero, bool bIgnoreErrorValues ) const +{ + CalcMaxMinValue<MaxOp> aFunc(bTextAsZero, bIgnoreErrorValues); + aFunc = maMat.walk(aFunc); + return aFunc.getValue(); +} + +double ScMatrixImpl::GetMinValue( bool bTextAsZero, bool bIgnoreErrorValues ) const +{ + CalcMaxMinValue<MinOp> aFunc(bTextAsZero, bIgnoreErrorValues); + aFunc = maMat.walk(aFunc); + return aFunc.getValue(); +} + +double ScMatrixImpl::GetGcd() const +{ + CalcGcdLcm<Gcd> aFunc; + aFunc = maMat.walk(aFunc); + return aFunc.getResult(); +} + +double ScMatrixImpl::GetLcm() const +{ + CalcGcdLcm<Lcm> aFunc; + aFunc = maMat.walk(aFunc); + return aFunc.getResult(); +} + +ScMatrixRef ScMatrixImpl::CompareMatrix( + sc::Compare& rComp, size_t nMatPos, sc::CompareOptions* pOptions ) const +{ + MatrixImplType::size_pair_type aSize = maMat.size(); + size_t nSize = aSize.column * aSize.row; + if (nMatPos == 0) + { + if (rComp.maCells[1].mbValue && !rComp.maCells[1].mbEmpty) + { + // Matrix on the left, and a numeric value on the right. Use a + // function object that has much less branching for much better + // performance. + CompareMatrixToNumericFunc aFunc(nSize, rComp, rComp.maCells[1].mfValue, pOptions); + aFunc = maMat.walk(std::move(aFunc)); + + // We assume the result matrix has the same dimension as this matrix. + const std::vector<double>& rResVal = aFunc.getValues(); + assert (nSize == rResVal.size()); + if (nSize != rResVal.size()) + return ScMatrixRef(); + + return ScMatrixRef(new ScMatrix(aSize.column, aSize.row, rResVal)); + } + } + + CompareMatrixFunc aFunc(nSize, rComp, nMatPos, pOptions); + aFunc = maMat.walk(std::move(aFunc)); + + // We assume the result matrix has the same dimension as this matrix. + const std::vector<double>& rResVal = aFunc.getValues(); + assert (nSize == rResVal.size()); + if (nSize != rResVal.size()) + return ScMatrixRef(); + + return ScMatrixRef(new ScMatrix(aSize.column, aSize.row, rResVal)); +} + +void ScMatrixImpl::GetDoubleArray( std::vector<double>& rArray, bool bEmptyAsZero ) const +{ + MatrixImplType::size_pair_type aSize = maMat.size(); + ToDoubleArray aFunc(aSize.row*aSize.column, bEmptyAsZero); + aFunc = maMat.walk(std::move(aFunc)); + aFunc.swap(rArray); +} + +void ScMatrixImpl::MergeDoubleArrayMultiply( std::vector<double>& rArray ) const +{ + MatrixImplType::size_pair_type aSize = maMat.size(); + size_t nSize = aSize.row*aSize.column; + if (nSize != rArray.size()) + return; + + MergeDoubleArrayFunc<ArrayMul> aFunc(rArray); + maMat.walk(std::move(aFunc)); +} + +namespace { + +template<typename T, typename U, typename return_type> +struct wrapped_iterator +{ + typedef ::std::bidirectional_iterator_tag iterator_category; + typedef typename T::const_iterator::value_type old_value_type; + typedef return_type value_type; + typedef value_type* pointer; + typedef value_type& reference; + typedef typename T::const_iterator::difference_type difference_type; + + typename T::const_iterator it; + mutable value_type val; + U maOp; + +private: + + value_type calcVal() const + { + return maOp(*it); + } + +public: + + wrapped_iterator(typename T::const_iterator it_, U const & aOp): + it(std::move(it_)), + val(value_type()), + maOp(aOp) + { + } + + wrapped_iterator(const wrapped_iterator& r): + it(r.it), + val(r.val), + maOp(r.maOp) + { + } + + wrapped_iterator& operator=(const wrapped_iterator& r) + { + it = r.it; + return *this; + } + + bool operator==(const wrapped_iterator& r) const + { + return it == r.it; + } + + bool operator!=(const wrapped_iterator& r) const + { + return !operator==(r); + } + + wrapped_iterator& operator++() + { + ++it; + + return *this; + } + + wrapped_iterator& operator--() + { + --it; + + return *this; + } + + value_type& operator*() const + { + val = calcVal(); + return val; + } + + pointer operator->() const + { + val = calcVal(); + return &val; + } +}; + +template<typename T, typename U, typename return_type> +struct MatrixIteratorWrapper +{ +private: + typename T::const_iterator m_itBegin; + typename T::const_iterator m_itEnd; + U maOp; +public: + MatrixIteratorWrapper(typename T::const_iterator itBegin, typename T::const_iterator itEnd, U const & aOp): + m_itBegin(std::move(itBegin)), + m_itEnd(std::move(itEnd)), + maOp(aOp) + { + } + + wrapped_iterator<T, U, return_type> begin() + { + return wrapped_iterator<T, U, return_type>(m_itBegin, maOp); + } + + wrapped_iterator<T, U, return_type> end() + { + return wrapped_iterator<T, U, return_type>(m_itEnd, maOp); + } +}; + +MatrixImplType::position_type increment_position(const MatrixImplType::position_type& pos, size_t n) +{ + MatrixImplType::position_type ret = pos; + do + { + if (ret.second + n < ret.first->size) + { + ret.second += n; + break; + } + else + { + n -= (ret.first->size - ret.second); + ++ret.first; + ret.second = 0; + } + } + while (n > 0); + return ret; +} + +template<typename T> +struct MatrixOpWrapper +{ +private: + MatrixImplType& mrMat; + MatrixImplType::position_type pos; + const T* mpOp; + +public: + MatrixOpWrapper(MatrixImplType& rMat, const T& aOp): + mrMat(rMat), + pos(rMat.position(0,0)), + mpOp(&aOp) + { + } + + MatrixOpWrapper( const MatrixOpWrapper& r ) : mrMat(r.mrMat), pos(r.pos), mpOp(r.mpOp) {} + + MatrixOpWrapper& operator= ( const MatrixOpWrapper& r ) = default; + + void operator()(const MatrixImplType::element_block_node_type& node) + { + switch (node.type) + { + case mdds::mtm::element_numeric: + { + typedef MatrixImplType::numeric_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + MatrixIteratorWrapper<block_type, T, typename T::number_value_type> aFunc(it, itEnd, *mpOp); + pos = mrMat.set(pos,aFunc.begin(), aFunc.end()); + } + break; + case mdds::mtm::element_boolean: + { + typedef MatrixImplType::boolean_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + + MatrixIteratorWrapper<block_type, T, typename T::number_value_type> aFunc(it, itEnd, *mpOp); + pos = mrMat.set(pos, aFunc.begin(), aFunc.end()); + } + break; + case mdds::mtm::element_string: + { + typedef MatrixImplType::string_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + block_type::const_iterator itEnd = block_type::end(*node.data); + + MatrixIteratorWrapper<block_type, T, typename T::number_value_type> aFunc(it, itEnd, *mpOp); + pos = mrMat.set(pos, aFunc.begin(), aFunc.end()); + } + break; + case mdds::mtm::element_empty: + { + if (mpOp->useFunctionForEmpty()) + { + std::vector<char> aVec(node.size); + MatrixIteratorWrapper<std::vector<char>, T, typename T::number_value_type> + aFunc(aVec.begin(), aVec.end(), *mpOp); + pos = mrMat.set(pos, aFunc.begin(), aFunc.end()); + } + } + break; + default: + ; + } + pos = increment_position(pos, node.size); + } +}; + +} + +template<typename T> +void ScMatrixImpl::ApplyOperation(T aOp, ScMatrixImpl& rMat) +{ + MatrixOpWrapper<T> aFunc(rMat.maMat, aOp); + maMat.walk(aFunc); +} + +template<typename T, typename tRes> +ScMatrix::IterateResultMultiple<tRes> ScMatrixImpl::ApplyCollectOperation(const std::vector<T>& aOp) +{ + WalkElementBlocksMultipleValues<T, tRes> aFunc(aOp); + aFunc = maMat.walk(std::move(aFunc)); + return aFunc.getResult(); +} + +namespace { + +struct ElementBlock +{ + ElementBlock(size_t nRowSize, + ScMatrix::DoubleOpFunction aDoubleFunc, + ScMatrix::BoolOpFunction aBoolFunc, + ScMatrix::StringOpFunction aStringFunc, + ScMatrix::EmptyOpFunction aEmptyFunc): + mnRowSize(nRowSize), + mnRowPos(0), + mnColPos(0), + maDoubleFunc(std::move(aDoubleFunc)), + maBoolFunc(std::move(aBoolFunc)), + maStringFunc(std::move(aStringFunc)), + maEmptyFunc(std::move(aEmptyFunc)) + { + } + + size_t mnRowSize; + size_t mnRowPos; + size_t mnColPos; + + ScMatrix::DoubleOpFunction maDoubleFunc; + ScMatrix::BoolOpFunction maBoolFunc; + ScMatrix::StringOpFunction maStringFunc; + ScMatrix::EmptyOpFunction maEmptyFunc; +}; + +class WalkElementBlockOperation +{ +public: + + WalkElementBlockOperation(ElementBlock& rElementBlock) + : mrElementBlock(rElementBlock) + { + } + + void operator()(const MatrixImplType::element_block_node_type& node) + { + switch (node.type) + { + case mdds::mtm::element_numeric: + { + typedef MatrixImplType::numeric_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + std::advance(it, node.offset); + block_type::const_iterator itEnd = it; + std::advance(itEnd, node.size); + for (auto itr = it; itr != itEnd; ++itr) + { + mrElementBlock.maDoubleFunc(mrElementBlock.mnRowPos, mrElementBlock.mnColPos, *itr); + ++mrElementBlock.mnRowPos; + if (mrElementBlock.mnRowPos >= mrElementBlock.mnRowSize) + { + mrElementBlock.mnRowPos = 0; + ++mrElementBlock.mnColPos; + } + } + } + break; + case mdds::mtm::element_string: + { + typedef MatrixImplType::string_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + std::advance(it, node.offset); + block_type::const_iterator itEnd = it; + std::advance(itEnd, node.size); + for (auto itr = it; itr != itEnd; ++itr) + { + mrElementBlock.maStringFunc(mrElementBlock.mnRowPos, mrElementBlock.mnColPos, *itr); + ++mrElementBlock.mnRowPos; + if (mrElementBlock.mnRowPos >= mrElementBlock.mnRowSize) + { + mrElementBlock.mnRowPos = 0; + ++mrElementBlock.mnColPos; + } + } + } + break; + case mdds::mtm::element_boolean: + { + typedef MatrixImplType::boolean_block_type block_type; + + block_type::const_iterator it = block_type::begin(*node.data); + std::advance(it, node.offset); + block_type::const_iterator itEnd = it; + std::advance(itEnd, node.size); + for (auto itr = it; itr != itEnd; ++itr) + { + mrElementBlock.maBoolFunc(mrElementBlock.mnRowPos, mrElementBlock.mnColPos, *itr); + ++mrElementBlock.mnRowPos; + if (mrElementBlock.mnRowPos >= mrElementBlock.mnRowSize) + { + mrElementBlock.mnRowPos = 0; + ++mrElementBlock.mnColPos; + } + } + } + break; + case mdds::mtm::element_empty: + { + for (size_t i=0; i < node.size; ++i) + { + mrElementBlock.maEmptyFunc(mrElementBlock.mnRowPos, mrElementBlock.mnColPos); + ++mrElementBlock.mnRowPos; + if (mrElementBlock.mnRowPos >= mrElementBlock.mnRowSize) + { + mrElementBlock.mnRowPos = 0; + ++mrElementBlock.mnColPos; + } + } + } + break; + case mdds::mtm::element_integer: + { + SAL_WARN("sc.core","WalkElementBlockOperation - unhandled element_integer"); + // No function (yet?), but advance row and column count. + mrElementBlock.mnColPos += node.size / mrElementBlock.mnRowSize; + mrElementBlock.mnRowPos += node.size % mrElementBlock.mnRowSize; + if (mrElementBlock.mnRowPos >= mrElementBlock.mnRowSize) + { + mrElementBlock.mnRowPos = 0; + ++mrElementBlock.mnColPos; + } + } + break; + } + } + +private: + + ElementBlock& mrElementBlock; +}; + +} + +void ScMatrixImpl::ExecuteOperation(const std::pair<size_t, size_t>& rStartPos, + const std::pair<size_t, size_t>& rEndPos, const ScMatrix::DoubleOpFunction& aDoubleFunc, + const ScMatrix::BoolOpFunction& aBoolFunc, const ScMatrix::StringOpFunction& aStringFunc, + const ScMatrix::EmptyOpFunction& aEmptyFunc) const +{ + ElementBlock aPayload(maMat.size().row, aDoubleFunc, aBoolFunc, aStringFunc, aEmptyFunc); + WalkElementBlockOperation aFunc(aPayload); + maMat.walk( + aFunc, + MatrixImplType::size_pair_type(rStartPos.first, rStartPos.second), + MatrixImplType::size_pair_type(rEndPos.first, rEndPos.second)); +} + +#if DEBUG_MATRIX + +void ScMatrixImpl::Dump() const +{ + cout << "-- matrix content" << endl; + SCSIZE nCols, nRows; + GetDimensions(nCols, nRows); + for (SCSIZE nRow = 0; nRow < nRows; ++nRow) + { + for (SCSIZE nCol = 0; nCol < nCols; ++nCol) + { + cout << " row=" << nRow << ", col=" << nCol << " : "; + switch (maMat.get_type(nRow, nCol)) + { + case mdds::mtm::element_string: + cout << "string (" << maMat.get_string(nRow, nCol).getString() << ")"; + break; + case mdds::mtm::element_numeric: + cout << "numeric (" << maMat.get_numeric(nRow, nCol) << ")"; + break; + case mdds::mtm::element_boolean: + cout << "boolean (" << maMat.get_boolean(nRow, nCol) << ")"; + break; + case mdds::mtm::element_empty: + cout << "empty"; + break; + default: + ; + } + + cout << endl; + } + } +} +#endif + +void ScMatrixImpl::CalcPosition(SCSIZE nIndex, SCSIZE& rC, SCSIZE& rR) const +{ + SCSIZE nRowSize = maMat.size().row; + SAL_WARN_IF( !nRowSize, "sc.core", "ScMatrixImpl::CalcPosition: 0 rows!"); + rC = nRowSize > 1 ? nIndex / nRowSize : nIndex; + rR = nIndex - rC*nRowSize; +} + +namespace { + +size_t get_index(SCSIZE nMaxRow, size_t nRow, size_t nCol, size_t nRowOffset, size_t nColOffset) +{ + return nMaxRow * (nCol + nColOffset) + nRow + nRowOffset; +} + +} + +void ScMatrixImpl::MatConcat(SCSIZE nMaxCol, SCSIZE nMaxRow, const ScMatrixRef& xMat1, const ScMatrixRef& xMat2, + SvNumberFormatter& rFormatter, svl::SharedStringPool& rStringPool) +{ + SCSIZE nC1, nC2; + SCSIZE nR1, nR2; + xMat1->GetDimensions(nC1, nR1); + xMat2->GetDimensions(nC2, nR2); + + sal_uInt32 nKey = rFormatter.GetStandardFormat( SvNumFormatType::NUMBER, + ScGlobal::eLnge); + + std::vector<OUString> aString(nMaxCol * nMaxRow); + std::vector<bool> aValid(nMaxCol * nMaxRow, true); + std::vector<FormulaError> nErrors(nMaxCol * nMaxRow,FormulaError::NONE); + + size_t nRowOffset = 0; + size_t nColOffset = 0; + std::function<void(size_t, size_t, double)> aDoubleFunc = + [&](size_t nRow, size_t nCol, double nVal) + { + FormulaError nErr = GetDoubleErrorValue(nVal); + if (nErr != FormulaError::NONE) + { + aValid[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)] = false; + nErrors[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)] = nErr; + return; + } + OUString aStr; + rFormatter.GetInputLineString( nVal, nKey, aStr); + aString[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)] = aString[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)] + aStr; + }; + + std::function<void(size_t, size_t, bool)> aBoolFunc = + [&](size_t nRow, size_t nCol, bool nVal) + { + OUString aStr; + rFormatter.GetInputLineString( nVal ? 1.0 : 0.0, nKey, aStr); + aString[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)] = aString[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)] + aStr; + }; + + std::function<void(size_t, size_t, const svl::SharedString&)> aStringFunc = + [&](size_t nRow, size_t nCol, const svl::SharedString& aStr) + { + aString[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)] = aString[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)] + aStr.getString(); + }; + + std::function<void(size_t, size_t)> aEmptyFunc = + [](size_t /*nRow*/, size_t /*nCol*/) + { + // Nothing. Concatenating an empty string to an existing string. + }; + + + if (nC1 == 1 || nR1 == 1) + { + size_t nRowRep = nR1 == 1 ? nMaxRow : 1; + size_t nColRep = nC1 == 1 ? nMaxCol : 1; + + for (size_t i = 0; i < nRowRep; ++i) + { + nRowOffset = i; + for (size_t j = 0; j < nColRep; ++j) + { + nColOffset = j; + xMat1->ExecuteOperation( + std::pair<size_t, size_t>(0, 0), + std::pair<size_t, size_t>(std::min(nR1, nMaxRow) - 1, std::min(nC1, nMaxCol) - 1), + aDoubleFunc, aBoolFunc, aStringFunc, aEmptyFunc); + } + } + } + else + xMat1->ExecuteOperation( + std::pair<size_t, size_t>(0, 0), + std::pair<size_t, size_t>(nMaxRow - 1, nMaxCol - 1), + std::move(aDoubleFunc), std::move(aBoolFunc), std::move(aStringFunc), std::move(aEmptyFunc)); + + std::vector<svl::SharedString> aSharedString(nMaxCol*nMaxRow); + + std::function<void(size_t, size_t, double)> aDoubleFunc2 = + [&](size_t nRow, size_t nCol, double nVal) + { + FormulaError nErr = GetDoubleErrorValue(nVal); + if (nErr != FormulaError::NONE) + { + aValid[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)] = false; + nErrors[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)] = nErr; + return; + } + OUString aStr; + rFormatter.GetInputLineString( nVal, nKey, aStr); + aSharedString[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)] = rStringPool.intern(aString[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)] + aStr); + }; + + std::function<void(size_t, size_t, bool)> aBoolFunc2 = + [&](size_t nRow, size_t nCol, bool nVal) + { + OUString aStr; + rFormatter.GetInputLineString( nVal ? 1.0 : 0.0, nKey, aStr); + aSharedString[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)] = rStringPool.intern(aString[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)] + aStr); + }; + + std::function<void(size_t, size_t, const svl::SharedString&)> aStringFunc2 = + [&](size_t nRow, size_t nCol, const svl::SharedString& aStr) + { + aSharedString[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)] = + rStringPool.intern(aString[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)] + aStr.getString()); + }; + + std::function<void(size_t, size_t)> aEmptyFunc2 = + [&](size_t nRow, size_t nCol) + { + aSharedString[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)] = + rStringPool.intern(aString[get_index(nMaxRow, nRow, nCol, nRowOffset, nColOffset)]); + }; + + nRowOffset = 0; + nColOffset = 0; + if (nC2 == 1 || nR2 == 1) + { + size_t nRowRep = nR2 == 1 ? nMaxRow : 1; + size_t nColRep = nC2 == 1 ? nMaxCol : 1; + + for (size_t i = 0; i < nRowRep; ++i) + { + nRowOffset = i; + for (size_t j = 0; j < nColRep; ++j) + { + nColOffset = j; + xMat2->ExecuteOperation( + std::pair<size_t, size_t>(0, 0), + std::pair<size_t, size_t>(std::min(nR2, nMaxRow) - 1, std::min(nC2, nMaxCol) - 1), + aDoubleFunc2, aBoolFunc2, aStringFunc2, aEmptyFunc2); + } + } + } + else + xMat2->ExecuteOperation( + std::pair<size_t, size_t>(0, 0), + std::pair<size_t, size_t>(nMaxRow - 1, nMaxCol - 1), + std::move(aDoubleFunc2), std::move(aBoolFunc2), std::move(aStringFunc2), std::move(aEmptyFunc2)); + + aString.clear(); + + MatrixImplType::position_type pos = maMat.position(0, 0); + for (SCSIZE i = 0; i < nMaxCol; ++i) + { + for (SCSIZE j = 0; j < nMaxRow && i < nMaxCol; ++j) + { + if (aValid[nMaxRow * i + j]) + { + auto itr = aValid.begin(); + std::advance(itr, nMaxRow * i + j); + auto itrEnd = std::find(itr, aValid.end(), false); + size_t nSteps = std::distance(itr, itrEnd); + auto itrStr = aSharedString.begin(); + std::advance(itrStr, nMaxRow * i + j); + auto itrEndStr = itrStr; + std::advance(itrEndStr, nSteps); + pos = maMat.set(pos, itrStr, itrEndStr); + size_t nColSteps = nSteps / nMaxRow; + i += nColSteps; + j += nSteps % nMaxRow; + if (j >= nMaxRow) + { + j -= nMaxRow; + ++i; + } + } + else + { + pos = maMat.set(pos, CreateDoubleError(nErrors[nMaxRow * i + j])); + } + pos = MatrixImplType::next_position(pos); + } + } +} + +bool ScMatrixImpl::IsValueOrEmpty( const MatrixImplType::const_position_type & rPos ) const +{ + switch (maMat.get_type(rPos)) + { + case mdds::mtm::element_boolean: + case mdds::mtm::element_numeric: + case mdds::mtm::element_empty: + return true; + default: + ; + } + return false; +} + +double ScMatrixImpl::GetDouble(const MatrixImplType::const_position_type & rPos) const +{ + double fVal = maMat.get_numeric(rPos); + if ( pErrorInterpreter ) + { + FormulaError nError = GetDoubleErrorValue(fVal); + if ( nError != FormulaError::NONE ) + SetErrorAtInterpreter( nError); + } + return fVal; +} + +FormulaError ScMatrixImpl::GetErrorIfNotString( const MatrixImplType::const_position_type & rPos ) const +{ return IsValue(rPos) ? GetError(rPos) : FormulaError::NONE; } + +bool ScMatrixImpl::IsValue( const MatrixImplType::const_position_type & rPos ) const +{ + switch (maMat.get_type(rPos)) + { + case mdds::mtm::element_boolean: + case mdds::mtm::element_numeric: + return true; + default: + ; + } + return false; +} + +FormulaError ScMatrixImpl::GetError(const MatrixImplType::const_position_type & rPos) const +{ + double fVal = maMat.get_numeric(rPos); + return GetDoubleErrorValue(fVal); +} + +bool ScMatrixImpl::IsStringOrEmpty(const MatrixImplType::const_position_type & rPos) const +{ + switch (maMat.get_type(rPos)) + { + case mdds::mtm::element_empty: + case mdds::mtm::element_string: + return true; + default: + ; + } + return false; +} + +void ScMatrixImpl::ExecuteBinaryOp(SCSIZE nMaxCol, SCSIZE nMaxRow, const ScMatrix& rInputMat1, const ScMatrix& rInputMat2, + ScInterpreter* pInterpreter, ScMatrix::CalculateOpFunction Op) +{ + // Check output matrix size, otherwise output iterator logic will be wrong. + assert(maMat.size().row == nMaxRow && maMat.size().column == nMaxCol + && "the caller code should have sized the output matrix to the passed dimensions"); + auto & rMatImpl1 = *rInputMat1.pImpl; + auto & rMatImpl2 = *rInputMat2.pImpl; + // Check if we can do fast-path, where we have no replication or mis-matched matrix sizes. + if (rMatImpl1.maMat.size() == rMatImpl2.maMat.size() + && rMatImpl1.maMat.size() == maMat.size()) + { + MatrixImplType::position_type aOutPos = maMat.position(0, 0); + MatrixImplType::const_position_type aPos1 = rMatImpl1.maMat.position(0, 0); + MatrixImplType::const_position_type aPos2 = rMatImpl2.maMat.position(0, 0); + for (SCSIZE i = 0; i < nMaxCol; i++) + { + for (SCSIZE j = 0; j < nMaxRow; j++) + { + bool bVal1 = rMatImpl1.IsValueOrEmpty(aPos1); + bool bVal2 = rMatImpl2.IsValueOrEmpty(aPos2); + FormulaError nErr; + if (bVal1 && bVal2) + { + double d = Op(rMatImpl1.GetDouble(aPos1), rMatImpl2.GetDouble(aPos2)); + aOutPos = maMat.set(aOutPos, d); + } + else if (((nErr = rMatImpl1.GetErrorIfNotString(aPos1)) != FormulaError::NONE) || + ((nErr = rMatImpl2.GetErrorIfNotString(aPos2)) != FormulaError::NONE)) + { + aOutPos = maMat.set(aOutPos, CreateDoubleError(nErr)); + } + else if ((!bVal1 && rMatImpl1.IsStringOrEmpty(aPos1)) || + (!bVal2 && rMatImpl2.IsStringOrEmpty(aPos2))) + { + FormulaError nError1 = FormulaError::NONE; + SvNumFormatType nFmt1 = SvNumFormatType::ALL; + double fVal1 = (bVal1 ? rMatImpl1.GetDouble(aPos1) : + pInterpreter->ConvertStringToValue( rMatImpl1.GetString(aPos1).getString(), nError1, nFmt1)); + + FormulaError nError2 = FormulaError::NONE; + SvNumFormatType nFmt2 = SvNumFormatType::ALL; + double fVal2 = (bVal2 ? rMatImpl2.GetDouble(aPos2) : + pInterpreter->ConvertStringToValue( rMatImpl2.GetString(aPos2).getString(), nError2, nFmt2)); + + if (nError1 != FormulaError::NONE) + aOutPos = maMat.set(aOutPos, CreateDoubleError(nError1)); + else if (nError2 != FormulaError::NONE) + aOutPos = maMat.set(aOutPos, CreateDoubleError(nError2)); + else + { + double d = Op( fVal1, fVal2); + aOutPos = maMat.set(aOutPos, d); + } + } + else + aOutPos = maMat.set(aOutPos, CreateDoubleError(FormulaError::NoValue)); + aPos1 = MatrixImplType::next_position(aPos1); + aPos2 = MatrixImplType::next_position(aPos2); + aOutPos = MatrixImplType::next_position(aOutPos); + } + } + } + else + { + // Noting that this block is very hard to optimise to use iterators, because various dodgy + // array function usage relies on the semantics of some of the methods we call here. + // (see unit test testDubiousArrayFormulasFODS). + // These methods are inconsistent in their usage of ValidColRowReplicated() vs. ValidColRowOrReplicated() + // which leads to some very odd results. + MatrixImplType::position_type aOutPos = maMat.position(0, 0); + for (SCSIZE i = 0; i < nMaxCol; i++) + { + for (SCSIZE j = 0; j < nMaxRow; j++) + { + bool bVal1 = rInputMat1.IsValueOrEmpty(i,j); + bool bVal2 = rInputMat2.IsValueOrEmpty(i,j); + FormulaError nErr; + if (bVal1 && bVal2) + { + double d = Op(rInputMat1.GetDouble(i,j), rInputMat2.GetDouble(i,j)); + aOutPos = maMat.set(aOutPos, d); + } + else if (((nErr = rInputMat1.GetErrorIfNotString(i,j)) != FormulaError::NONE) || + ((nErr = rInputMat2.GetErrorIfNotString(i,j)) != FormulaError::NONE)) + { + aOutPos = maMat.set(aOutPos, CreateDoubleError(nErr)); + } + else if ((!bVal1 && rInputMat1.IsStringOrEmpty(i,j)) || (!bVal2 && rInputMat2.IsStringOrEmpty(i,j))) + { + FormulaError nError1 = FormulaError::NONE; + SvNumFormatType nFmt1 = SvNumFormatType::ALL; + double fVal1 = (bVal1 ? rInputMat1.GetDouble(i,j) : + pInterpreter->ConvertStringToValue( rInputMat1.GetString(i,j).getString(), nError1, nFmt1)); + + FormulaError nError2 = FormulaError::NONE; + SvNumFormatType nFmt2 = SvNumFormatType::ALL; + double fVal2 = (bVal2 ? rInputMat2.GetDouble(i,j) : + pInterpreter->ConvertStringToValue( rInputMat2.GetString(i,j).getString(), nError2, nFmt2)); + + if (nError1 != FormulaError::NONE) + aOutPos = maMat.set(aOutPos, CreateDoubleError(nError1)); + else if (nError2 != FormulaError::NONE) + aOutPos = maMat.set(aOutPos, CreateDoubleError(nError2)); + else + { + double d = Op( fVal1, fVal2); + aOutPos = maMat.set(aOutPos, d); + } + } + else + aOutPos = maMat.set(aOutPos, CreateDoubleError(FormulaError::NoValue)); + aOutPos = MatrixImplType::next_position(aOutPos); + } + } + } +} + +void ScMatrix::IncRef() const +{ + ++nRefCnt; +} + +void ScMatrix::DecRef() const +{ + --nRefCnt; + if (nRefCnt == 0) + delete this; +} + +bool ScMatrix::IsSizeAllocatable( SCSIZE nC, SCSIZE nR ) +{ + SAL_WARN_IF( !nC, "sc.core", "ScMatrix with 0 columns!"); + SAL_WARN_IF( !nR, "sc.core", "ScMatrix with 0 rows!"); + // 0-size matrix is valid, it could be resized later. + if ((nC && !nR) || (!nC && nR)) + { + SAL_WARN( "sc.core", "ScMatrix one-dimensional zero: " << nC << " columns * " << nR << " rows"); + return false; + } + if (!nC || !nR) + return true; + + std::call_once(bElementsMaxFetched, + []() + { + const char* pEnv = std::getenv("SC_MAX_MATRIX_ELEMENTS"); + if (pEnv) + { + // Environment specifies the overall elements pool. + nElementsMax = std::atoi(pEnv); + } + else + { + // GetElementsMax() uses an (~arbitrary) elements limit. + // The actual allocation depends on the types of individual matrix + // elements and is averaged for type double. +#if SAL_TYPES_SIZEOFPOINTER < 8 + // Assume 1GB memory could be consumed by matrices. + constexpr size_t nMemMax = 0x40000000; +#else + // Assume 6GB memory could be consumed by matrices. + constexpr size_t nMemMax = 0x180000000; +#endif + nElementsMax = GetElementsMax( nMemMax); + } + }); + + if (nC > (nElementsMax / nR)) + { + SAL_WARN( "sc.core", "ScMatrix overflow: " << nC << " columns * " << nR << " rows"); + return false; + } + return true; +} + +ScMatrix::ScMatrix( SCSIZE nC, SCSIZE nR) : + nRefCnt(0), mbCloneIfConst(true) +{ + if (ScMatrix::IsSizeAllocatable( nC, nR)) + pImpl.reset( new ScMatrixImpl( nC, nR)); + else + // Invalid matrix size, allocate 1x1 matrix with error value. + pImpl.reset( new ScMatrixImpl( 1,1, CreateDoubleError( FormulaError::MatrixSize))); +} + +ScMatrix::ScMatrix(SCSIZE nC, SCSIZE nR, double fInitVal) : + nRefCnt(0), mbCloneIfConst(true) +{ + if (ScMatrix::IsSizeAllocatable( nC, nR)) + pImpl.reset( new ScMatrixImpl( nC, nR, fInitVal)); + else + // Invalid matrix size, allocate 1x1 matrix with error value. + pImpl.reset( new ScMatrixImpl( 1,1, CreateDoubleError( FormulaError::MatrixSize))); +} + +ScMatrix::ScMatrix( size_t nC, size_t nR, const std::vector<double>& rInitVals ) : + nRefCnt(0), mbCloneIfConst(true) +{ + if (ScMatrix::IsSizeAllocatable( nC, nR)) + pImpl.reset( new ScMatrixImpl( nC, nR, rInitVals)); + else + // Invalid matrix size, allocate 1x1 matrix with error value. + pImpl.reset( new ScMatrixImpl( 1,1, CreateDoubleError( FormulaError::MatrixSize))); +} + +ScMatrix::~ScMatrix() +{ +} + +ScMatrix* ScMatrix::Clone() const +{ + SCSIZE nC, nR; + pImpl->GetDimensions(nC, nR); + ScMatrix* pScMat = new ScMatrix(nC, nR); + MatCopy(*pScMat); + pScMat->SetErrorInterpreter(pImpl->GetErrorInterpreter()); // TODO: really? + return pScMat; +} + +ScMatrix* ScMatrix::CloneIfConst() +{ + return mbCloneIfConst ? Clone() : this; +} + +void ScMatrix::SetMutable() +{ + mbCloneIfConst = false; +} + +void ScMatrix::SetImmutable() const +{ + mbCloneIfConst = true; +} + +void ScMatrix::Resize( SCSIZE nC, SCSIZE nR) +{ + pImpl->Resize(nC, nR); +} + +void ScMatrix::Resize(SCSIZE nC, SCSIZE nR, double fVal) +{ + pImpl->Resize(nC, nR, fVal); +} + +ScMatrix* ScMatrix::CloneAndExtend(SCSIZE nNewCols, SCSIZE nNewRows) const +{ + ScMatrix* pScMat = new ScMatrix(nNewCols, nNewRows); + MatCopy(*pScMat); + pScMat->SetErrorInterpreter(pImpl->GetErrorInterpreter()); + return pScMat; +} + +void ScMatrix::SetErrorInterpreter( ScInterpreter* p) +{ + pImpl->SetErrorInterpreter(p); +} + +void ScMatrix::GetDimensions( SCSIZE& rC, SCSIZE& rR) const +{ + pImpl->GetDimensions(rC, rR); +} + +SCSIZE ScMatrix::GetElementCount() const +{ + return pImpl->GetElementCount(); +} + +bool ScMatrix::ValidColRow( SCSIZE nC, SCSIZE nR) const +{ + return pImpl->ValidColRow(nC, nR); +} + +bool ScMatrix::ValidColRowReplicated( SCSIZE & rC, SCSIZE & rR ) const +{ + return pImpl->ValidColRowReplicated(rC, rR); +} + +bool ScMatrix::ValidColRowOrReplicated( SCSIZE & rC, SCSIZE & rR ) const +{ + return ValidColRow( rC, rR) || ValidColRowReplicated( rC, rR); +} + +void ScMatrix::PutDouble(double fVal, SCSIZE nC, SCSIZE nR) +{ + pImpl->PutDouble(fVal, nC, nR); +} + +void ScMatrix::PutDouble( double fVal, SCSIZE nIndex) +{ + pImpl->PutDouble(fVal, nIndex); +} + +void ScMatrix::PutDouble(const double* pArray, size_t nLen, SCSIZE nC, SCSIZE nR) +{ + pImpl->PutDouble(pArray, nLen, nC, nR); +} + +void ScMatrix::PutString(const svl::SharedString& rStr, SCSIZE nC, SCSIZE nR) +{ + pImpl->PutString(rStr, nC, nR); +} + +void ScMatrix::PutString(const svl::SharedString& rStr, SCSIZE nIndex) +{ + pImpl->PutString(rStr, nIndex); +} + +void ScMatrix::PutString(const svl::SharedString* pArray, size_t nLen, SCSIZE nC, SCSIZE nR) +{ + pImpl->PutString(pArray, nLen, nC, nR); +} + +void ScMatrix::PutEmpty(SCSIZE nC, SCSIZE nR) +{ + pImpl->PutEmpty(nC, nR); +} + +void ScMatrix::PutEmptyPath(SCSIZE nC, SCSIZE nR) +{ + pImpl->PutEmptyPath(nC, nR); +} + +void ScMatrix::PutError( FormulaError nErrorCode, SCSIZE nC, SCSIZE nR ) +{ + pImpl->PutError(nErrorCode, nC, nR); +} + +void ScMatrix::PutBoolean(bool bVal, SCSIZE nC, SCSIZE nR) +{ + pImpl->PutBoolean(bVal, nC, nR); +} + +FormulaError ScMatrix::GetError( SCSIZE nC, SCSIZE nR) const +{ + return pImpl->GetError(nC, nR); +} + +double ScMatrix::GetDouble(SCSIZE nC, SCSIZE nR) const +{ + return pImpl->GetDouble(nC, nR); +} + +double ScMatrix::GetDouble( SCSIZE nIndex) const +{ + return pImpl->GetDouble(nIndex); +} + +double ScMatrix::GetDoubleWithStringConversion(SCSIZE nC, SCSIZE nR) const +{ + return pImpl->GetDoubleWithStringConversion(nC, nR); +} + +svl::SharedString ScMatrix::GetString(SCSIZE nC, SCSIZE nR) const +{ + return pImpl->GetString(nC, nR); +} + +svl::SharedString ScMatrix::GetString( SCSIZE nIndex) const +{ + return pImpl->GetString(nIndex); +} + +svl::SharedString ScMatrix::GetString( SvNumberFormatter& rFormatter, SCSIZE nC, SCSIZE nR) const +{ + return pImpl->GetString(rFormatter, nC, nR); +} + +ScMatrixValue ScMatrix::Get(SCSIZE nC, SCSIZE nR) const +{ + return pImpl->Get(nC, nR); +} + +bool ScMatrix::IsStringOrEmpty( SCSIZE nIndex ) const +{ + return pImpl->IsStringOrEmpty(nIndex); +} + +bool ScMatrix::IsStringOrEmpty( SCSIZE nC, SCSIZE nR ) const +{ + return pImpl->IsStringOrEmpty(nC, nR); +} + +bool ScMatrix::IsEmpty( SCSIZE nC, SCSIZE nR ) const +{ + return pImpl->IsEmpty(nC, nR); +} + +bool ScMatrix::IsEmptyCell( SCSIZE nC, SCSIZE nR ) const +{ + return pImpl->IsEmptyCell(nC, nR); +} + +bool ScMatrix::IsEmptyResult( SCSIZE nC, SCSIZE nR ) const +{ + return pImpl->IsEmptyResult(nC, nR); +} + +bool ScMatrix::IsEmptyPath( SCSIZE nC, SCSIZE nR ) const +{ + return pImpl->IsEmptyPath(nC, nR); +} + +bool ScMatrix::IsValue( SCSIZE nIndex ) const +{ + return pImpl->IsValue(nIndex); +} + +bool ScMatrix::IsValue( SCSIZE nC, SCSIZE nR ) const +{ + return pImpl->IsValue(nC, nR); +} + +bool ScMatrix::IsValueOrEmpty( SCSIZE nC, SCSIZE nR ) const +{ + return pImpl->IsValueOrEmpty(nC, nR); +} + +bool ScMatrix::IsBoolean( SCSIZE nC, SCSIZE nR ) const +{ + return pImpl->IsBoolean(nC, nR); +} + +bool ScMatrix::IsNumeric() const +{ + return pImpl->IsNumeric(); +} + +void ScMatrix::MatCopy(const ScMatrix& mRes) const +{ + pImpl->MatCopy(*mRes.pImpl); +} + +void ScMatrix::MatTrans(const ScMatrix& mRes) const +{ + pImpl->MatTrans(*mRes.pImpl); +} + +void ScMatrix::FillDouble( double fVal, SCSIZE nC1, SCSIZE nR1, SCSIZE nC2, SCSIZE nR2 ) +{ + pImpl->FillDouble(fVal, nC1, nR1, nC2, nR2); +} + +void ScMatrix::PutDoubleVector( const ::std::vector< double > & rVec, SCSIZE nC, SCSIZE nR ) +{ + pImpl->PutDoubleVector(rVec, nC, nR); +} + +void ScMatrix::PutStringVector( const ::std::vector< svl::SharedString > & rVec, SCSIZE nC, SCSIZE nR ) +{ + pImpl->PutStringVector(rVec, nC, nR); +} + +void ScMatrix::PutEmptyVector( SCSIZE nCount, SCSIZE nC, SCSIZE nR ) +{ + pImpl->PutEmptyVector(nCount, nC, nR); +} + +void ScMatrix::PutEmptyResultVector( SCSIZE nCount, SCSIZE nC, SCSIZE nR ) +{ + pImpl->PutEmptyResultVector(nCount, nC, nR); +} + +void ScMatrix::PutEmptyPathVector( SCSIZE nCount, SCSIZE nC, SCSIZE nR ) +{ + pImpl->PutEmptyPathVector(nCount, nC, nR); +} + +void ScMatrix::CompareEqual() +{ + pImpl->CompareEqual(); +} + +void ScMatrix::CompareNotEqual() +{ + pImpl->CompareNotEqual(); +} + +void ScMatrix::CompareLess() +{ + pImpl->CompareLess(); +} + +void ScMatrix::CompareGreater() +{ + pImpl->CompareGreater(); +} + +void ScMatrix::CompareLessEqual() +{ + pImpl->CompareLessEqual(); +} + +void ScMatrix::CompareGreaterEqual() +{ + pImpl->CompareGreaterEqual(); +} + +double ScMatrix::And() const +{ + return pImpl->And(); +} + +double ScMatrix::Or() const +{ + return pImpl->Or(); +} + +double ScMatrix::Xor() const +{ + return pImpl->Xor(); +} + +ScMatrix::KahanIterateResult ScMatrix::Sum(bool bTextAsZero, bool bIgnoreErrorValues) const +{ + return pImpl->Sum(bTextAsZero, bIgnoreErrorValues); +} + +ScMatrix::KahanIterateResult ScMatrix::SumSquare(bool bTextAsZero, bool bIgnoreErrorValues) const +{ + return pImpl->SumSquare(bTextAsZero, bIgnoreErrorValues); +} + +ScMatrix::DoubleIterateResult ScMatrix::Product(bool bTextAsZero, bool bIgnoreErrorValues) const +{ + return pImpl->Product(bTextAsZero, bIgnoreErrorValues); +} + +size_t ScMatrix::Count(bool bCountStrings, bool bCountErrors, bool bIgnoreEmptyStrings) const +{ + return pImpl->Count(bCountStrings, bCountErrors, bIgnoreEmptyStrings); +} + +size_t ScMatrix::MatchDoubleInColumns(double fValue, size_t nCol1, size_t nCol2) const +{ + return pImpl->MatchDoubleInColumns(fValue, nCol1, nCol2); +} + +size_t ScMatrix::MatchStringInColumns(const svl::SharedString& rStr, size_t nCol1, size_t nCol2) const +{ + return pImpl->MatchStringInColumns(rStr, nCol1, nCol2); +} + +double ScMatrix::GetMaxValue( bool bTextAsZero, bool bIgnoreErrorValues ) const +{ + return pImpl->GetMaxValue(bTextAsZero, bIgnoreErrorValues); +} + +double ScMatrix::GetMinValue( bool bTextAsZero, bool bIgnoreErrorValues ) const +{ + return pImpl->GetMinValue(bTextAsZero, bIgnoreErrorValues); +} + +double ScMatrix::GetGcd() const +{ + return pImpl->GetGcd(); +} + +double ScMatrix::GetLcm() const +{ + return pImpl->GetLcm(); +} + + +ScMatrixRef ScMatrix::CompareMatrix( + sc::Compare& rComp, size_t nMatPos, sc::CompareOptions* pOptions ) const +{ + return pImpl->CompareMatrix(rComp, nMatPos, pOptions); +} + +void ScMatrix::GetDoubleArray( std::vector<double>& rArray, bool bEmptyAsZero ) const +{ + pImpl->GetDoubleArray(rArray, bEmptyAsZero); +} + +void ScMatrix::MergeDoubleArrayMultiply( std::vector<double>& rArray ) const +{ + pImpl->MergeDoubleArrayMultiply(rArray); +} + +namespace matop { + +namespace { + +/** A template for operations where operands are supposed to be numeric. + A non-numeric (string) operand leads to the configured conversion to number + method being called if in interpreter context and a FormulaError::NoValue DoubleError + if conversion was not possible, else to an unconditional FormulaError::NoValue + DoubleError. + An empty operand evaluates to 0. + */ +template<typename TOp> +struct MatOp +{ +private: + TOp maOp; + ScInterpreter* mpErrorInterpreter; + double mfVal; + +public: + typedef double number_value_type; + + MatOp( TOp aOp, ScInterpreter* pErrorInterpreter, + double fVal = 0.0 ): + maOp(aOp), + mpErrorInterpreter(pErrorInterpreter), + mfVal(fVal) + { + if (mpErrorInterpreter) + { + FormulaError nErr = mpErrorInterpreter->GetError(); + if (nErr != FormulaError::NONE) + mfVal = CreateDoubleError( nErr); + } + } + + double operator()(double fVal) const + { + return maOp(fVal, mfVal); + } + + double operator()(bool bVal) const + { + return maOp(static_cast<double>(bVal), mfVal); + } + + double operator()(const svl::SharedString& rStr) const + { + return maOp( convertStringToValue( mpErrorInterpreter, rStr.getString()), mfVal); + } + + /// the action for empty entries in a matrix + double operator()(char) const + { + return maOp(0, mfVal); + } + + static bool useFunctionForEmpty() + { + return true; + } +}; + +} + +} + +void ScMatrix::NotOp( const ScMatrix& rMat) +{ + auto not_ = [](double a, double){return double(a == 0.0);}; + matop::MatOp<decltype(not_)> aOp(not_, pImpl->GetErrorInterpreter()); + pImpl->ApplyOperation(aOp, *rMat.pImpl); +} + +void ScMatrix::NegOp( const ScMatrix& rMat) +{ + auto neg_ = [](double a, double){return -a;}; + matop::MatOp<decltype(neg_)> aOp(neg_, pImpl->GetErrorInterpreter()); + pImpl->ApplyOperation(aOp, *rMat.pImpl); +} + +void ScMatrix::AddOp( double fVal, const ScMatrix& rMat) +{ + auto add_ = [](double a, double b){return a + b;}; + matop::MatOp<decltype(add_)> aOp(add_, pImpl->GetErrorInterpreter(), fVal); + pImpl->ApplyOperation(aOp, *rMat.pImpl); +} + +void ScMatrix::SubOp( bool bFlag, double fVal, const ScMatrix& rMat) +{ + if (bFlag) + { + auto sub_ = [](double a, double b){return b - a;}; + matop::MatOp<decltype(sub_)> aOp(sub_, pImpl->GetErrorInterpreter(), fVal); + pImpl->ApplyOperation(aOp, *rMat.pImpl); + } + else + { + auto sub_ = [](double a, double b){return a - b;}; + matop::MatOp<decltype(sub_)> aOp(sub_, pImpl->GetErrorInterpreter(), fVal); + pImpl->ApplyOperation(aOp, *rMat.pImpl); + } +} + +void ScMatrix::MulOp( double fVal, const ScMatrix& rMat) +{ + auto mul_ = [](double a, double b){return a * b;}; + matop::MatOp<decltype(mul_)> aOp(mul_, pImpl->GetErrorInterpreter(), fVal); + pImpl->ApplyOperation(aOp, *rMat.pImpl); +} + +void ScMatrix::DivOp( bool bFlag, double fVal, const ScMatrix& rMat) +{ + if (bFlag) + { + auto div_ = [](double a, double b){return sc::div(b, a);}; + matop::MatOp<decltype(div_)> aOp(div_, pImpl->GetErrorInterpreter(), fVal); + pImpl->ApplyOperation(aOp, *rMat.pImpl); + } + else + { + auto div_ = [](double a, double b){return sc::div(a, b);}; + matop::MatOp<decltype(div_)> aOp(div_, pImpl->GetErrorInterpreter(), fVal); + pImpl->ApplyOperation(aOp, *rMat.pImpl); + } +} + +void ScMatrix::PowOp( bool bFlag, double fVal, const ScMatrix& rMat) +{ + if (bFlag) + { + auto pow_ = [](double a, double b){return sc::power(b, a);}; + matop::MatOp<decltype(pow_)> aOp(pow_, pImpl->GetErrorInterpreter(), fVal); + pImpl->ApplyOperation(aOp, *rMat.pImpl); + } + else + { + auto pow_ = [](double a, double b){return sc::power(a, b);}; + matop::MatOp<decltype(pow_)> aOp(pow_, pImpl->GetErrorInterpreter(), fVal); + pImpl->ApplyOperation(aOp, *rMat.pImpl); + } +} + +void ScMatrix::ExecuteOperation(const std::pair<size_t, size_t>& rStartPos, + const std::pair<size_t, size_t>& rEndPos, DoubleOpFunction aDoubleFunc, + BoolOpFunction aBoolFunc, StringOpFunction aStringFunc, EmptyOpFunction aEmptyFunc) const +{ + pImpl->ExecuteOperation(rStartPos, rEndPos, aDoubleFunc, aBoolFunc, aStringFunc, aEmptyFunc); +} + +ScMatrix::KahanIterateResultMultiple ScMatrix::CollectKahan(const std::vector<sc::op::kOp>& aOp) +{ + return pImpl->ApplyCollectOperation<sc::op::kOp, KahanSum>(aOp); +} + +#if DEBUG_MATRIX +void ScMatrix::Dump() const +{ + pImpl->Dump(); +} +#endif + +void ScMatrix::MatConcat(SCSIZE nMaxCol, SCSIZE nMaxRow, + const ScMatrixRef& xMat1, const ScMatrixRef& xMat2, SvNumberFormatter& rFormatter, svl::SharedStringPool& rPool) +{ + pImpl->MatConcat(nMaxCol, nMaxRow, xMat1, xMat2, rFormatter, rPool); +} + +void ScMatrix::ExecuteBinaryOp(SCSIZE nMaxCol, SCSIZE nMaxRow, const ScMatrix& rInputMat1, const ScMatrix& rInputMat2, + ScInterpreter* pInterpreter, CalculateOpFunction op) +{ + pImpl->ExecuteBinaryOp(nMaxCol, nMaxRow, rInputMat1, rInputMat2, pInterpreter, op); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/scopetools.cxx b/sc/source/core/tool/scopetools.cxx new file mode 100644 index 0000000000..38ca8c2527 --- /dev/null +++ b/sc/source/core/tool/scopetools.cxx @@ -0,0 +1,122 @@ +/* -*- 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/. + */ + +#include <scopetools.hxx> +#include <document.hxx> +#include <column.hxx> + +namespace sc { + +AutoCalcSwitch::AutoCalcSwitch(ScDocument& rDoc, bool bAutoCalc) : + mrDoc(rDoc), mbOldValue(rDoc.GetAutoCalc()) +{ + mrDoc.SetAutoCalc(bAutoCalc); +} + +AutoCalcSwitch::~AutoCalcSwitch() +{ + mrDoc.SetAutoCalc(mbOldValue); +} + +ExpandRefsSwitch::ExpandRefsSwitch(ScDocument& rDoc, bool bExpandRefs) : + mrDoc(rDoc), mbOldValue(rDoc.IsExpandRefs()) +{ + mrDoc.SetExpandRefs(bExpandRefs); +} + +ExpandRefsSwitch::~ExpandRefsSwitch() +{ + mrDoc.SetExpandRefs(mbOldValue); +} + +UndoSwitch::UndoSwitch(ScDocument& rDoc, bool bUndo) : + mrDoc(rDoc), mbOldValue(rDoc.IsUndoEnabled()) +{ + mrDoc.EnableUndo(bUndo); +} + +UndoSwitch::~UndoSwitch() +{ + mrDoc.EnableUndo(mbOldValue); +} + +IdleSwitch::IdleSwitch(ScDocument& rDoc, bool bEnableIdle) : + mrDoc(rDoc), mbOldValue(rDoc.IsIdleEnabled()) +{ + mrDoc.EnableIdle(bEnableIdle); +} + +IdleSwitch::~IdleSwitch() +{ + mrDoc.EnableIdle(mbOldValue); +} + +DelayFormulaGroupingSwitch::DelayFormulaGroupingSwitch(ScDocument& rDoc, bool delay) : + mrDoc(rDoc), mbOldValue(rDoc.IsDelayedFormulaGrouping()) +{ + mrDoc.DelayFormulaGrouping(delay); +} + +DelayFormulaGroupingSwitch::~DelayFormulaGroupingSwitch() COVERITY_NOEXCEPT_FALSE +{ + mrDoc.DelayFormulaGrouping(mbOldValue); +} + +void DelayFormulaGroupingSwitch::reset() +{ + mrDoc.DelayFormulaGrouping(mbOldValue); +} + +DelayStartListeningFormulaCells::DelayStartListeningFormulaCells(ScColumn& column, bool delay) + : mColumn(column), mbOldValue(column.GetDoc().IsEnabledDelayStartListeningFormulaCells(&column)) +{ + column.GetDoc().EnableDelayStartListeningFormulaCells(&column, delay); +} + +DelayStartListeningFormulaCells::DelayStartListeningFormulaCells(ScColumn& column) + : mColumn(column), mbOldValue(column.GetDoc().IsEnabledDelayStartListeningFormulaCells(&column)) +{ +} + +DelayStartListeningFormulaCells::~DelayStartListeningFormulaCells() +{ +#if defined(__COVERITY__) + try + { + mColumn.GetDoc().EnableDelayStartListeningFormulaCells(&mColumn, mbOldValue); + } + catch (...) + { + std::abort(); + } +#else + mColumn.GetDoc().EnableDelayStartListeningFormulaCells(&mColumn, mbOldValue); +#endif +} + +void DelayStartListeningFormulaCells::set() +{ + mColumn.GetDoc().EnableDelayStartListeningFormulaCells(&mColumn, true); +} + +DelayDeletingBroadcasters::DelayDeletingBroadcasters(ScDocument& doc) + : mDoc( doc ) + , mOldValue( mDoc.IsDelayedDeletingBroadcasters()) +{ + mDoc.EnableDelayDeletingBroadcasters( true ); +} + +DelayDeletingBroadcasters::~DelayDeletingBroadcasters() +{ + suppress_fun_call_w_exception(mDoc.EnableDelayDeletingBroadcasters(mOldValue)); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/sharedformula.cxx b/sc/source/core/tool/sharedformula.cxx new file mode 100644 index 0000000000..7680aac405 --- /dev/null +++ b/sc/source/core/tool/sharedformula.cxx @@ -0,0 +1,442 @@ +/* -*- 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/. + */ + +#include <sharedformula.hxx> +#include <calcmacros.hxx> +#include <tokenarray.hxx> +#include <listenercontext.hxx> +#include <document.hxx> +#include <grouparealistener.hxx> +#include <refdata.hxx> + +namespace sc { + +const ScFormulaCell* SharedFormulaUtil::getSharedTopFormulaCell(const CellStoreType::position_type& aPos) +{ + if (aPos.first->type != sc::element_type_formula) + // Not a formula cell block. + return nullptr; + + sc::formula_block::iterator it = sc::formula_block::begin(*aPos.first->data); + std::advance(it, aPos.second); + const ScFormulaCell* pCell = *it; + if (!pCell->IsShared()) + // Not a shared formula. + return nullptr; + + return pCell->GetCellGroup()->mpTopCell; +} + +bool SharedFormulaUtil::splitFormulaCellGroup(const CellStoreType::position_type& aPos, sc::EndListeningContext* pCxt) +{ + SCROW nRow = aPos.first->position + aPos.second; + + if (aPos.first->type != sc::element_type_formula) + // Not a formula cell block. + return false; + + if (aPos.second == 0) + // Split position coincides with the block border. Nothing to do. + return false; + + sc::formula_block::iterator it = sc::formula_block::begin(*aPos.first->data); + std::advance(it, aPos.second); + ScFormulaCell& rTop = **it; + if (!rTop.IsShared()) + // Not a shared formula. + return false; + + if (nRow == rTop.GetSharedTopRow()) + // Already the top cell of a shared group. + return false; + + ScFormulaCellGroupRef xGroup = rTop.GetCellGroup(); + + SCROW nLength2 = xGroup->mpTopCell->aPos.Row() + xGroup->mnLength - nRow; + ScFormulaCellGroupRef xGroup2; + if (nLength2 > 1) + { + xGroup2.reset(new ScFormulaCellGroup); + xGroup2->mbInvariant = xGroup->mbInvariant; + xGroup2->mpTopCell = &rTop; + xGroup2->mnLength = nLength2; + xGroup2->mpCode = xGroup->mpCode->CloneValue(); + } + + xGroup->mnLength = nRow - xGroup->mpTopCell->aPos.Row(); + ScFormulaCell& rPrevTop = *sc::formula_block::at(*aPos.first->data, aPos.second - xGroup->mnLength); + +#if USE_FORMULA_GROUP_LISTENER + // At least group area listeners will have to be adapted. As long as + // there's no update mechanism and no separated handling of group area and + // other listeners, all listeners of this group's top cell are to be reset. + if (nLength2) + { + // If a context exists it has to be used to not interfere with + // ScColumn::maBroadcasters iterators, which the EndListeningTo() + // without context would do when removing a broadcaster that had its + // last listener removed. + if (pCxt) + rPrevTop.EndListeningTo(*pCxt); + else + rPrevTop.EndListeningTo( rPrevTop.GetDocument(), nullptr, ScAddress( ScAddress::UNINITIALIZED)); + rPrevTop.SetNeedsListening(true); + + // The new group or remaining single cell needs a new listening. + rTop.SetNeedsListening(true); + } +#endif + + if (xGroup->mnLength == 1) + { + // The top group consists of only one cell. Ungroup this. + ScFormulaCellGroupRef xNone; + rPrevTop.SetCellGroup(xNone); + } + + // Apply the lower group object to the lower cells. + assert ((xGroup2 == nullptr || xGroup2->mpTopCell->aPos.Row() + size_t(xGroup2->mnLength) <= aPos.first->position + aPos.first->size) + && "Shared formula region goes beyond the formula block."); + sc::formula_block::iterator itEnd = it; + std::advance(itEnd, nLength2); + for (; it != itEnd; ++it) + { + ScFormulaCell& rCell = **it; + rCell.SetCellGroup(xGroup2); + } + + return true; +} + +bool SharedFormulaUtil::splitFormulaCellGroups(const ScDocument& rDoc, CellStoreType& rCells, std::vector<SCROW>& rBounds) +{ + if (rBounds.empty()) + return false; + + // Sort and remove duplicates. + std::sort(rBounds.begin(), rBounds.end()); + std::vector<SCROW>::iterator it = std::unique(rBounds.begin(), rBounds.end()); + rBounds.erase(it, rBounds.end()); + + it = rBounds.begin(); + SCROW nRow = *it; + CellStoreType::position_type aPos = rCells.position(nRow); + if (aPos.first == rCells.end()) + return false; + + bool bSplit = splitFormulaCellGroup(aPos, nullptr); + std::vector<SCROW>::iterator itEnd = rBounds.end(); + for (++it; it != itEnd; ++it) + { + nRow = *it; + if (rDoc.ValidRow(nRow)) + { + aPos = rCells.position(aPos.first, nRow); + if (aPos.first == rCells.end()) + return bSplit; + bSplit |= splitFormulaCellGroup(aPos, nullptr); + } + } + return bSplit; +} + +bool SharedFormulaUtil::joinFormulaCells( + const CellStoreType::position_type& rPos, ScFormulaCell& rCell1, ScFormulaCell& rCell2 ) +{ + if( rCell1.GetDocument().IsDelayedFormulaGrouping()) + { + rCell1.GetDocument().AddDelayedFormulaGroupingCell( &rCell1 ); + rCell1.GetDocument().AddDelayedFormulaGroupingCell( &rCell2 ); + return false; + } + + ScFormulaCell::CompareState eState = rCell1.CompareByTokenArray(rCell2); + if (eState == ScFormulaCell::NotEqual) + return false; + + // Formula tokens equal those of the previous formula cell. + ScFormulaCellGroupRef xGroup1 = rCell1.GetCellGroup(); + ScFormulaCellGroupRef xGroup2 = rCell2.GetCellGroup(); + if (xGroup1) + { + if (xGroup2) + { + // Both cell 1 and cell 2 are shared. Merge them together. + if (xGroup1.get() == xGroup2.get()) + // They belong to the same group. + return false; + + // Set the group object from cell 1 to all cells in group 2. + xGroup1->mnLength += xGroup2->mnLength; + size_t nOffset = rPos.second + 1; // position of cell 2 + for (size_t i = 0, n = xGroup2->mnLength; i < n; ++i) + { + ScFormulaCell& rCell = *sc::formula_block::at(*rPos.first->data, nOffset+i); + rCell.SetCellGroup(xGroup1); + } + } + else + { + // cell 1 is shared but cell 2 is not. + rCell2.SetCellGroup(xGroup1); + ++xGroup1->mnLength; + } + } + else + { + if (xGroup2) + { + // cell 1 is not shared, but cell 2 is already shared. + rCell1.SetCellGroup(xGroup2); + xGroup2->mpTopCell = &rCell1; + ++xGroup2->mnLength; + } + else + { + // neither cells are shared. + assert(rCell1.aPos.Row() == static_cast<SCROW>(rPos.first->position + rPos.second)); + xGroup1 = rCell1.CreateCellGroup(2, eState == ScFormulaCell::EqualInvariant); + rCell2.SetCellGroup(xGroup1); + } + } + + return true; +} + +bool SharedFormulaUtil::joinFormulaCellAbove( const CellStoreType::position_type& aPos ) +{ + if (aPos.first->type != sc::element_type_formula) + // This is not a formula cell. + return false; + + if (aPos.second == 0) + // This cell is already the top cell in a formula block; the previous + // cell is not a formula cell. + return false; + + ScFormulaCell& rPrev = *sc::formula_block::at(*aPos.first->data, aPos.second-1); + ScFormulaCell& rCell = *sc::formula_block::at(*aPos.first->data, aPos.second); + sc::CellStoreType::position_type aPosPrev = aPos; + --aPosPrev.second; + return joinFormulaCells(aPosPrev, rPrev, rCell); +} + +void SharedFormulaUtil::unshareFormulaCell(const CellStoreType::position_type& aPos, ScFormulaCell& rCell) +{ + if (!rCell.IsShared()) + return; + + ScFormulaCellGroupRef xNone; + sc::CellStoreType::iterator it = aPos.first; + + // This formula cell is shared. Adjust the shared group. + if (rCell.aPos.Row() == rCell.GetSharedTopRow()) + { + // Top of the shared range. + const ScFormulaCellGroupRef& xGroup = rCell.GetCellGroup(); + if (xGroup->mnLength == 2) + { + // Group consists of only two cells. Mark the second one non-shared. + assert (aPos.second+1 < aPos.first->size + && "There is no next formula cell but there should be!"); + ScFormulaCell& rNext = *sc::formula_block::at(*it->data, aPos.second+1); + rNext.SetCellGroup(xNone); + } + else + { + // Move the top cell to the next formula cell down. + ScFormulaCell& rNext = *sc::formula_block::at(*it->data, aPos.second+1); + xGroup->mpTopCell = &rNext; + } + --xGroup->mnLength; + } + else if (rCell.aPos.Row() == rCell.GetSharedTopRow() + rCell.GetSharedLength() - 1) + { + // Bottom of the shared range. + const ScFormulaCellGroupRef& xGroup = rCell.GetCellGroup(); + if (xGroup->mnLength == 2) + { + // Mark the top cell non-shared. + assert(aPos.second != 0 && "There is no previous formula cell but there should be!"); + ScFormulaCell& rPrev = *sc::formula_block::at(*it->data, aPos.second-1); + rPrev.SetCellGroup(xNone); + } + else + { + // Just shorten the shared range length by one. + --xGroup->mnLength; + } + } + else + { + // In the middle of the shared range. Split it into two groups. + ScFormulaCellGroupRef xGroup = rCell.GetCellGroup(); + SCROW nEndRow = xGroup->mpTopCell->aPos.Row() + xGroup->mnLength - 1; + xGroup->mnLength = rCell.aPos.Row() - xGroup->mpTopCell->aPos.Row(); // Shorten the top group. + if (xGroup->mnLength == 1) + { + // Make the top cell non-shared. + assert(aPos.second != 0 && "There is no previous formula cell but there should be!"); + ScFormulaCell& rPrev = *sc::formula_block::at(*it->data, aPos.second-1); + rPrev.SetCellGroup(xNone); + } + + SCROW nLength2 = nEndRow - rCell.aPos.Row(); + if (nLength2 >= 2) + { + ScFormulaCellGroupRef xGroup2; + xGroup2.reset(new ScFormulaCellGroup); + ScFormulaCell& rNext = *sc::formula_block::at(*it->data, aPos.second+1); + xGroup2->mpTopCell = &rNext; + xGroup2->mnLength = nLength2; + xGroup2->mbInvariant = xGroup->mbInvariant; + xGroup2->mpCode = xGroup->mpCode->CloneValue(); + assert(xGroup2->mpTopCell->aPos.Row() + size_t(xGroup2->mnLength) <= it->position + it->size + && "Shared formula region goes beyond the formula block."); + sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data); + std::advance(itCell, aPos.second+1); + sc::formula_block::iterator itCellEnd = itCell; + std::advance(itCellEnd, xGroup2->mnLength); + for (; itCell != itCellEnd; ++itCell) + { + ScFormulaCell& rCell2 = **itCell; + rCell2.SetCellGroup(xGroup2); + } + } + else + { + // Make the next cell non-shared. + sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data); + std::advance(itCell, aPos.second+1); + ScFormulaCell& rCell2 = **itCell; + rCell2.SetCellGroup(xNone); + } + } + + rCell.SetCellGroup(xNone); +} + +void SharedFormulaUtil::unshareFormulaCells(const ScDocument& rDoc, CellStoreType& rCells, std::vector<SCROW>& rRows) +{ + if (rRows.empty()) + return; + + // Sort and remove duplicates. + std::sort(rRows.begin(), rRows.end()); + rRows.erase(std::unique(rRows.begin(), rRows.end()), rRows.end()); + + // Add next cell positions to the list (to ensure that each position becomes a single cell). + std::vector<SCROW> aRows2; + for (const auto& rRow : rRows) + { + if (rRow > rDoc.MaxRow()) + break; + + aRows2.push_back(rRow); + + if (rRow < rDoc.MaxRow()) + aRows2.push_back(rRow+1); + } + + // Remove duplicates again (the vector should still be sorted). + aRows2.erase(std::unique(aRows2.begin(), aRows2.end()), aRows2.end()); + + splitFormulaCellGroups(rDoc, rCells, aRows2); +} + +void SharedFormulaUtil::startListeningAsGroup( sc::StartListeningContext& rCxt, ScFormulaCell** ppSharedTop ) +{ + ScFormulaCell& rTopCell = **ppSharedTop; + assert(rTopCell.IsSharedTop()); + +#if USE_FORMULA_GROUP_LISTENER + ScDocument& rDoc = rCxt.getDoc(); + rDoc.SetDetectiveDirty(true); + + ScFormulaCellGroupRef xGroup = rTopCell.GetCellGroup(); + const ScTokenArray& rCode = *xGroup->mpCode; + assert(&rCode == rTopCell.GetCode()); + if (rCode.IsRecalcModeAlways()) + { + rDoc.StartListeningArea( + BCA_LISTEN_ALWAYS, false, + xGroup->getAreaListener(ppSharedTop, BCA_LISTEN_ALWAYS, true, true)); + } + + formula::FormulaToken** p = rCode.GetCode(); + formula::FormulaToken** pEnd = p + rCode.GetCodeLen(); + for (; p != pEnd; ++p) + { + const formula::FormulaToken* t = *p; + switch (t->GetType()) + { + case formula::svSingleRef: + { + const ScSingleRefData* pRef = t->GetSingleRef(); + ScAddress aPos = pRef->toAbs(rDoc, rTopCell.aPos); + ScFormulaCell** pp = ppSharedTop; + ScFormulaCell** ppEnd = ppSharedTop + xGroup->mnLength; + for (; pp != ppEnd; ++pp) + { + if (!aPos.IsValid()) + break; + + rDoc.StartListeningCell(rCxt, aPos, **pp); + if (pRef->IsRowRel()) + aPos.IncRow(); + } + } + break; + case formula::svDoubleRef: + { + const ScSingleRefData& rRef1 = *t->GetSingleRef(); + const ScSingleRefData& rRef2 = *t->GetSingleRef2(); + ScAddress aPos1 = rRef1.toAbs(rDoc, rTopCell.aPos); + ScAddress aPos2 = rRef2.toAbs(rDoc, rTopCell.aPos); + + ScRange aOrigRange(aPos1, aPos2); + ScRange aListenedRange = aOrigRange; + if (rRef2.IsRowRel()) + aListenedRange.aEnd.IncRow(xGroup->mnLength-1); + + if (aPos1.IsValid() && aPos2.IsValid()) + { + rDoc.StartListeningArea( + aListenedRange, true, + xGroup->getAreaListener(ppSharedTop, aOrigRange, !rRef1.IsRowRel(), !rRef2.IsRowRel())); + } + } + break; + default: + ; + } + } + + ScFormulaCell** pp = ppSharedTop; + ScFormulaCell** ppEnd = ppSharedTop + xGroup->mnLength; + for (; pp != ppEnd; ++pp) + { + ScFormulaCell& rCell = **pp; + rCell.SetNeedsListening(false); + } + +#else + ScFormulaCell** pp = ppSharedTop; + ScFormulaCell** ppEnd = ppSharedTop + rTopCell.GetSharedLength(); + for (; pp != ppEnd; ++pp) + { + ScFormulaCell& rFC = **pp; + rFC.StartListeningTo(rCxt); + } +#endif +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/sharedstringpoolpurge.cxx b/sc/source/core/tool/sharedstringpoolpurge.cxx new file mode 100644 index 0000000000..7b8749006e --- /dev/null +++ b/sc/source/core/tool/sharedstringpoolpurge.cxx @@ -0,0 +1,58 @@ +/* -*- 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/. + * + */ + +#include <sharedstringpoolpurge.hxx> + +#include <algorithm> + +#include <vcl/svapp.hxx> + +namespace sc +{ +SharedStringPoolPurge::SharedStringPoolPurge() + : mTimer("SharedStringPoolPurge") +{ + mTimer.SetPriority(TaskPriority::LOWEST); + mTimer.SetTimeout(10000); // 10 sec + mTimer.SetInvokeHandler(LINK(this, SharedStringPoolPurge, timerHandler)); +} + +SharedStringPoolPurge::~SharedStringPoolPurge() { cleanup(); } + +void SharedStringPoolPurge::delayedPurge(const std::shared_ptr<svl::SharedStringPool>& pool) +{ + if (std::find(mPoolsToPurge.begin(), mPoolsToPurge.end(), pool) == mPoolsToPurge.end()) + { + mPoolsToPurge.push_back(pool); + SolarMutexGuard guard; + mTimer.Start(); + } +} + +void SharedStringPoolPurge::cleanup() +{ + for (std::shared_ptr<svl::SharedStringPool>& pool : mPoolsToPurge) + { + if (pool.use_count() > 1) + pool->purge(); + } + mPoolsToPurge.clear(); +} + +IMPL_LINK_NOARG(SharedStringPoolPurge, timerHandler, Timer*, void) +{ + SolarMutexGuard guard; + mTimer.Stop(); + cleanup(); +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/stringutil.cxx b/sc/source/core/tool/stringutil.cxx new file mode 100644 index 0000000000..8a436386ae --- /dev/null +++ b/sc/source/core/tool/stringutil.cxx @@ -0,0 +1,469 @@ +/* -*- 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 <stringutil.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> + +#include <rtl/ustrbuf.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/math.hxx> + +ScSetStringParam::ScSetStringParam() : + mpNumFormatter(nullptr), + mbDetectNumberFormat(true), + mbDetectScientificNumberFormat(true), + meSetTextNumFormat(Never), + mbHandleApostrophe(true), + meStartListening(sc::SingleCellListening), + mbCheckLinkFormula(false) +{ +} + +void ScSetStringParam::setTextInput() +{ + mbDetectNumberFormat = false; + mbDetectScientificNumberFormat = false; + mbHandleApostrophe = false; + meSetTextNumFormat = Always; +} + +void ScSetStringParam::setNumericInput() +{ + mbDetectNumberFormat = true; + mbDetectScientificNumberFormat = true; + mbHandleApostrophe = true; + meSetTextNumFormat = Never; +} + +bool ScStringUtil::parseSimpleNumber( + const OUString& rStr, sal_Unicode dsep, sal_Unicode gsep, sal_Unicode dsepa, double& rVal, bool bDetectScientificNumber) +{ + // Actually almost the entire pre-check is unnecessary and we could call + // rtl::math::stringToDouble() just after having exchanged ascii space with + // non-breaking space, if it wasn't for check of grouped digits. The NaN + // and Inf cases that are accepted by stringToDouble() could be detected + // using std::isfinite() on the result. + + /* TODO: The grouped digits check isn't even valid for locales that do not + * group in thousands ... e.g. Indian locales. But that's something also + * the number scanner doesn't implement yet, only the formatter. */ + + OUStringBuffer aBuf; + + sal_Int32 i = 0; + sal_Int32 n = rStr.getLength(); + const sal_Unicode* p = rStr.getStr(); + const sal_Unicode* pLast = p + (n-1); + sal_Int32 nPosDSep = -1, nPosGSep = -1; + sal_uInt32 nDigitCount = 0; + bool haveSeenDigit = false; + sal_Int32 nPosExponent = -1; + + // Skip preceding spaces. + for (i = 0; i < n; ++i, ++p) + { + sal_Unicode c = *p; + if (c != 0x0020 && c != 0x00A0) + // first non-space character. Exit. + break; + } + + if (i == n) + // the whole string is space. Fail. + return false; + + n -= i; // Subtract the length of the preceding spaces. + + // Determine the last non-space character. + for (; p != pLast; --pLast, --n) + { + sal_Unicode c = *pLast; + if (c != 0x0020 && c != 0x00A0) + // Non space character. Exit. + break; + } + + for (i = 0; i < n; ++i, ++p) + { + sal_Unicode c = *p; + if (c == 0x0020 && gsep == 0x00A0) + // ascii space to unicode space if that is group separator + c = 0x00A0; + + if ('0' <= c && c <= '9') + { + // this is a digit. + aBuf.append(c); + haveSeenDigit = true; + ++nDigitCount; + } + else if (c == dsep || (dsepa && c == dsepa)) + { + // this is a decimal separator. + + if (nPosDSep >= 0) + // a second decimal separator -> not a valid number. + return false; + + if (nPosGSep >= 0 && i - nPosGSep != 4) + // the number has a group separator and the decimal sep is not + // positioned correctly. + return false; + + nPosDSep = i; + nPosGSep = -1; + aBuf.append(dsep); // append the separator that is parsed in stringToDouble() below + nDigitCount = 0; + } + else if (c == gsep) + { + // this is a group (thousand) separator. + + if (!haveSeenDigit) + // not allowed before digits. + return false; + + if (nPosDSep >= 0) + // not allowed after the decimal separator. + return false; + + if (nPosGSep >= 0 && nDigitCount != 3) + // must be exactly 3 digits since the last group separator. + return false; + + if (nPosExponent >= 0) + // not allowed in exponent. + return false; + + nPosGSep = i; + nDigitCount = 0; + } + else if (c == '-' || c == '+') + { + // A sign must be the first character if it's given, or immediately + // follow the exponent character if present. + if (i == 0 || (nPosExponent >= 0 && i == nPosExponent + 1)) + aBuf.append(c); + else + return false; + } + else if (c == 'E' || c == 'e') + { + // this is an exponent designator. + + if (nPosExponent >= 0 || !bDetectScientificNumber) + // Only one exponent allowed. + return false; + + if (nPosGSep >= 0 && nDigitCount != 3) + // must be exactly 3 digits since the last group separator. + return false; + + aBuf.append(c); + nPosExponent = i; + nPosDSep = -1; + nPosGSep = -1; + nDigitCount = 0; + } + else + return false; + } + + // finished parsing the number. + + if (nPosGSep >= 0 && nDigitCount != 3) + // must be exactly 3 digits since the last group separator. + return false; + + rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok; + sal_Int32 nParseEnd = 0; + rVal = ::rtl::math::stringToDouble( aBuf, dsep, gsep, &eStatus, &nParseEnd); + if (eStatus != rtl_math_ConversionStatus_Ok || nParseEnd < aBuf.getLength()) + // Not a valid number or not entire string consumed. + return false; + + return true; +} + +bool ScStringUtil::parseSimpleNumber( + const char* p, size_t n, char dsep, char gsep, double& rVal) +{ + // Actually almost the entire pre-check is unnecessary and we could call + // rtl::math::stringToDouble() just after having exchanged ascii space with + // non-breaking space, if it wasn't for check of grouped digits. The NaN + // and Inf cases that are accepted by stringToDouble() could be detected + // using std::isfinite() on the result. + + /* TODO: The grouped digits check isn't even valid for locales that do not + * group in thousands ... e.g. Indian locales. But that's something also + * the number scanner doesn't implement yet, only the formatter. */ + + OStringBuffer aBuf; + + size_t i = 0; + const char* pLast = p + (n-1); + sal_Int32 nPosDSep = -1, nPosGSep = -1; + sal_uInt32 nDigitCount = 0; + bool haveSeenDigit = false; + sal_Int32 nPosExponent = -1; + + // Skip preceding spaces. + for (i = 0; i < n; ++i, ++p) + { + char c = *p; + if (c != ' ') + // first non-space character. Exit. + break; + } + + if (i == n) + // the whole string is space. Fail. + return false; + + n -= i; // Subtract the length of the preceding spaces. + + // Determine the last non-space character. + for (; p != pLast; --pLast, --n) + { + char c = *pLast; + if (c != ' ') + // Non space character. Exit. + break; + } + + for (i = 0; i < n; ++i, ++p) + { + char c = *p; + + if ('0' <= c && c <= '9') + { + // this is a digit. + aBuf.append(c); + haveSeenDigit = true; + ++nDigitCount; + } + else if (c == dsep) + { + // this is a decimal separator. + + if (nPosDSep >= 0) + // a second decimal separator -> not a valid number. + return false; + + if (nPosGSep >= 0 && i - nPosGSep != 4) + // the number has a group separator and the decimal sep is not + // positioned correctly. + return false; + + nPosDSep = i; + nPosGSep = -1; + aBuf.append(c); + nDigitCount = 0; + } + else if (c == gsep) + { + // this is a group (thousand) separator. + + if (!haveSeenDigit) + // not allowed before digits. + return false; + + if (nPosDSep >= 0) + // not allowed after the decimal separator. + return false; + + if (nPosGSep >= 0 && nDigitCount != 3) + // must be exactly 3 digits since the last group separator. + return false; + + if (nPosExponent >= 0) + // not allowed in exponent. + return false; + + nPosGSep = i; + nDigitCount = 0; + } + else if (c == '-' || c == '+') + { + // A sign must be the first character if it's given, or immediately + // follow the exponent character if present. + if (i == 0 || (nPosExponent >= 0 && i == static_cast<size_t>(nPosExponent+1))) + aBuf.append(c); + else + return false; + } + else if (c == 'E' || c == 'e') + { + // this is an exponent designator. + + if (nPosExponent >= 0) + // Only one exponent allowed. + return false; + + if (nPosGSep >= 0 && nDigitCount != 3) + // must be exactly 3 digits since the last group separator. + return false; + + aBuf.append(c); + nPosExponent = i; + nPosDSep = -1; + nPosGSep = -1; + nDigitCount = 0; + } + else + return false; + } + + // finished parsing the number. + + if (nPosGSep >= 0 && nDigitCount != 3) + // must be exactly 3 digits since the last group separator. + return false; + + rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok; + sal_Int32 nParseEnd = 0; + rVal = ::rtl::math::stringToDouble( aBuf, dsep, gsep, &eStatus, &nParseEnd); + if (eStatus != rtl_math_ConversionStatus_Ok || nParseEnd < aBuf.getLength()) + // Not a valid number or not entire string consumed. + return false; + + return true; +} + +OUString ScStringUtil::GetQuotedToken(const OUString &rIn, sal_Int32 nToken, const OUString& rQuotedPairs, + sal_Unicode cTok, sal_Int32& rIndex ) +{ + assert( !(rQuotedPairs.getLength()%2) ); + assert( rQuotedPairs.indexOf(cTok) == -1 ); + + const sal_Unicode* pStr = rIn.getStr(); + const sal_Unicode* pQuotedStr = rQuotedPairs.getStr(); + sal_Unicode cQuotedEndChar = 0; + sal_Int32 nQuotedLen = rQuotedPairs.getLength(); + sal_Int32 nLen = rIn.getLength(); + sal_Int32 nTok = 0; + sal_Int32 nFirstChar = rIndex; + sal_Int32 i = nFirstChar; + + // detect token position and length + pStr += i; + while ( i < nLen ) + { + sal_Unicode c = *pStr; + if ( cQuotedEndChar ) + { + // end of the quote reached ? + if ( c == cQuotedEndChar ) + cQuotedEndChar = 0; + } + else + { + // Is the char a quote-begin char ? + sal_Int32 nQuoteIndex = 0; + while ( nQuoteIndex < nQuotedLen ) + { + if ( pQuotedStr[nQuoteIndex] == c ) + { + cQuotedEndChar = pQuotedStr[nQuoteIndex+1]; + break; + } + else + nQuoteIndex += 2; + } + + // If the token-char matches then increase TokCount + if ( c == cTok ) + { + ++nTok; + + if ( nTok == nToken ) + nFirstChar = i+1; + else + { + if ( nTok > nToken ) + break; + } + } + } + + ++pStr; + ++i; + } + + if ( nTok >= nToken ) + { + if ( i < nLen ) + rIndex = i+1; + else + rIndex = -1; + return rIn.copy( nFirstChar, i-nFirstChar ); + } + else + { + rIndex = -1; + return OUString(); + } +} + +bool ScStringUtil::isMultiline( std::u16string_view rStr ) +{ + return rStr.find_first_of(u"\n\r") != std::u16string_view::npos; +} + +ScInputStringType ScStringUtil::parseInputString( + SvNumberFormatter& rFormatter, const OUString& rStr, LanguageType eLang ) +{ + ScInputStringType aRet; + aRet.mnFormatType = SvNumFormatType::ALL; + aRet.meType = ScInputStringType::Unknown; + aRet.maText = rStr; + aRet.mfValue = 0.0; + + if (rStr.getLength() > 1 && rStr[0] == '=') + { + aRet.meType = ScInputStringType::Formula; + } + else if (rStr.getLength() > 1 && rStr[0] == '\'') + { + // for bEnglish, "'" at the beginning is always interpreted as text + // marker and stripped + aRet.maText = rStr.copy(1); + aRet.meType = ScInputStringType::Text; + } + else // test for English number format (only) + { + sal_uInt32 nNumFormat = rFormatter.GetStandardIndex(eLang); + + if (rFormatter.IsNumberFormat(rStr, nNumFormat, aRet.mfValue)) + { + aRet.meType = ScInputStringType::Number; + aRet.mnFormatType = rFormatter.GetType(nNumFormat); + } + else if (!rStr.isEmpty()) + aRet.meType = ScInputStringType::Text; + + // the (English) number format is not set + //TODO: find and replace with matching local format??? + } + + return aRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/stylehelper.cxx b/sc/source/core/tool/stylehelper.cxx new file mode 100644 index 0000000000..bdd580ee32 --- /dev/null +++ b/sc/source/core/tool/stylehelper.cxx @@ -0,0 +1,173 @@ +/* -*- 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 <sal/config.h> + +#include <string_view> + +#include <svl/style.hxx> +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> + +#include <stylehelper.hxx> +#include <globstr.hrc> +#include <scresid.hxx> + +namespace { + +struct ScDisplayNameMap +{ + OUString aDispName; + OUString aProgName; +}; + +} + +static const ScDisplayNameMap* lcl_GetStyleNameMap( SfxStyleFamily nType ) +{ + if ( nType == SfxStyleFamily::Para ) + { + static ScDisplayNameMap const aCellMap[] + { + // Standard builtin styles from configuration. + // Defined in sc/res/xml/styles.xml + // Installed to "$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/calc/styles.xml" + // e.g. /usr/lib64/libreoffice/share/calc/styles.xml + // or instdir/share/calc/styles.xml + { ScResId( STR_STYLENAME_HEADING ), "Heading" }, + { ScResId( STR_STYLENAME_HEADING_1 ), "Heading 1" }, + { ScResId( STR_STYLENAME_HEADING_2 ), "Heading 2" }, + { ScResId( STR_STYLENAME_TEXT ), "Text" }, + { ScResId( STR_STYLENAME_NOTE ), "Note" }, + { ScResId( STR_STYLENAME_FOOTNOTE ), "Footnote" }, + { ScResId( STR_STYLENAME_HYPERLINK ), "Hyperlink" }, + { ScResId( STR_STYLENAME_STATUS ), "Status" }, + { ScResId( STR_STYLENAME_GOOD ), "Good" }, + { ScResId( STR_STYLENAME_NEUTRAL ), "Neutral" }, + { ScResId( STR_STYLENAME_BAD ), "Bad" }, + { ScResId( STR_STYLENAME_WARNING ), "Warning" }, + { ScResId( STR_STYLENAME_ERROR ), "Error" }, + { ScResId( STR_STYLENAME_ACCENT ), "Accent" }, + { ScResId( STR_STYLENAME_ACCENT_1 ), "Accent 1" }, + { ScResId( STR_STYLENAME_ACCENT_2 ), "Accent 2" }, + { ScResId( STR_STYLENAME_ACCENT_3 ), "Accent 3" }, + { ScResId( STR_STYLENAME_RESULT ), "Result" }, + // API compatibility programmatic names after. + { ScResId( STR_STYLENAME_STANDARD ), SC_STYLE_PROG_STANDARD }, + { ScResId( STR_STYLENAME_RESULT ), SC_STYLE_PROG_RESULT }, + { ScResId( STR_STYLENAME_RESULT1 ), SC_STYLE_PROG_RESULT1 }, + { ScResId( STR_STYLENAME_HEADING ), SC_STYLE_PROG_HEADING }, + { ScResId( STR_STYLENAME_HEADING_1 ), SC_STYLE_PROG_HEADING1 }, + // Pivot table styles. + { ScResId( STR_PIVOT_STYLENAME_INNER ), SC_PIVOT_STYLE_PROG_INNER }, + { ScResId( STR_PIVOT_STYLENAME_RESULT ), SC_PIVOT_STYLE_PROG_RESULT }, + { ScResId( STR_PIVOT_STYLENAME_CATEGORY ), SC_PIVOT_STYLE_PROG_CATEGORY }, + { ScResId( STR_PIVOT_STYLENAME_TITLE ), SC_PIVOT_STYLE_PROG_TITLE }, + { ScResId( STR_PIVOT_STYLENAME_FIELDNAME ), SC_PIVOT_STYLE_PROG_FIELDNAME }, + { ScResId( STR_PIVOT_STYLENAME_TOP ), SC_PIVOT_STYLE_PROG_TOP }, + // last entry remains empty + { OUString(), OUString() }, + }; + return aCellMap; + } + else if ( nType == SfxStyleFamily::Page ) + { + static ScDisplayNameMap const aPageMap[] + { + { ScResId( STR_STYLENAME_STANDARD ), SC_STYLE_PROG_STANDARD }, + { ScResId( STR_STYLENAME_REPORT ), SC_STYLE_PROG_REPORT }, + // last entry remains empty + { OUString(), OUString() }, + }; + return aPageMap; + } + else if ( nType == SfxStyleFamily::Frame ) + { + static ScDisplayNameMap const aGraphicMap[] + { + { ScResId( STR_STYLENAME_STANDARD ), SC_STYLE_PROG_STANDARD }, + { ScResId( STR_STYLENAME_NOTE ), "Note" }, + // last entry remains empty + { OUString(), OUString() }, + }; + return aGraphicMap; + } + OSL_FAIL("invalid family"); + return nullptr; +} + +// programmatic name suffix for display names that match other programmatic names +// is " (user)" including a space + +constexpr OUString SC_SUFFIX_USER = u" (user)"_ustr; + +static bool lcl_EndsWithUser( std::u16string_view rString ) +{ + return o3tl::ends_with(rString, SC_SUFFIX_USER); +} + +OUString ScStyleNameConversion::DisplayToProgrammaticName( const OUString& rDispName, SfxStyleFamily nType ) +{ + bool bDisplayIsProgrammatic = false; + + const ScDisplayNameMap* pNames = lcl_GetStyleNameMap( nType ); + if (pNames) + { + do + { + if (pNames->aDispName == rDispName) + return pNames->aProgName; + else if (pNames->aProgName == rDispName) + bDisplayIsProgrammatic = true; // display name matches any programmatic name + } + while( !(++pNames)->aDispName.isEmpty() ); + } + + if ( bDisplayIsProgrammatic || lcl_EndsWithUser( rDispName ) ) + { + // add the (user) suffix if the display name matches any style's programmatic name + // or if it already contains the suffix + return rDispName + SC_SUFFIX_USER; + } + + return rDispName; +} + +OUString ScStyleNameConversion::ProgrammaticToDisplayName( const OUString& rProgName, SfxStyleFamily nType ) +{ + if ( lcl_EndsWithUser( rProgName ) ) + { + // remove the (user) suffix, don't compare to map entries + return rProgName.copy( 0, rProgName.getLength() - SC_SUFFIX_USER.getLength() ); + } + + const ScDisplayNameMap* pNames = lcl_GetStyleNameMap( nType ); + if (pNames) + { + do + { + if (pNames->aProgName == rProgName) + return pNames->aDispName; + } + while( !(++pNames)->aDispName.isEmpty() ); + } + return rProgName; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/subtotal.cxx b/sc/source/core/tool/subtotal.cxx new file mode 100644 index 0000000000..4c213893b1 --- /dev/null +++ b/sc/source/core/tool/subtotal.cxx @@ -0,0 +1,204 @@ +/* -*- 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 <subtotal.hxx> +#include <sal/mathconf.h> +#include <cfloat> + +bool SubTotal::SafePlus(double& fVal1, double fVal2) +{ + bool bOk = true; + SAL_MATH_FPEXCEPTIONS_OFF(); + fVal1 += fVal2; + if (!std::isfinite(fVal1)) + { + bOk = false; + if (fVal2 > 0.0) + fVal1 = DBL_MAX; + else + fVal1 = -DBL_MAX; + } + return bOk; +} + +bool SubTotal::SafeMult(double& fVal1, double fVal2) +{ + bool bOk = true; + SAL_MATH_FPEXCEPTIONS_OFF(); + fVal1 *= fVal2; + if (!std::isfinite(fVal1)) + { + bOk = false; + fVal1 = DBL_MAX; + } + return bOk; +} + +bool SubTotal::SafeDiv(double& fVal1, double fVal2) +{ + bool bOk = true; + SAL_MATH_FPEXCEPTIONS_OFF(); + fVal1 /= fVal2; + if (!std::isfinite(fVal1)) + { + bOk = false; + fVal1 = DBL_MAX; + } + return bOk; +} + +void ScFunctionData::update(double fNewVal) +{ + if (mbError) + return; + + switch (meFunc) + { + case SUBTOTAL_FUNC_SUM: + if (!SubTotal::SafePlus(getValueRef(), fNewVal)) + mbError = true; + break; + case SUBTOTAL_FUNC_PROD: + if (getCountRef() == 0) // copy first value (nVal is initialized to 0) + { + getValueRef() = fNewVal; + getCountRef() = 1; // don't care about further count + } + else if (!SubTotal::SafeMult(getValueRef(), fNewVal)) + mbError = true; + break; + case SUBTOTAL_FUNC_CNT: + case SUBTOTAL_FUNC_CNT2: + ++getCountRef(); + break; + case SUBTOTAL_FUNC_SELECTION_COUNT: + getCountRef() += fNewVal; + break; + case SUBTOTAL_FUNC_AVE: + if (!SubTotal::SafePlus(getValueRef(), fNewVal)) + mbError = true; + else + ++getCountRef(); + break; + case SUBTOTAL_FUNC_MAX: + if (getCountRef() == 0) // copy first value (nVal is initialized to 0) + { + getValueRef() = fNewVal; + getCountRef() = 1; // don't care about further count + } + else if (fNewVal > getValueRef()) + getValueRef() = fNewVal; + break; + case SUBTOTAL_FUNC_MIN: + if (getCountRef() == 0) // copy first value (nVal is initialized to 0) + { + getValueRef() = fNewVal; + getCountRef() = 1; // don't care about further count + } + else if (fNewVal < getValueRef()) + getValueRef() = fNewVal; + break; + case SUBTOTAL_FUNC_VAR: + case SUBTOTAL_FUNC_STD: + case SUBTOTAL_FUNC_VARP: + case SUBTOTAL_FUNC_STDP: + maWelford.update(fNewVal); + break; + default: + // unhandled unknown + mbError = true; + } +} + +double ScFunctionData::getResult() +{ + if (mbError) + return 0.0; + + double fRet = 0.0; + switch (meFunc) + { + case SUBTOTAL_FUNC_CNT: + case SUBTOTAL_FUNC_CNT2: + case SUBTOTAL_FUNC_SELECTION_COUNT: + fRet = getCountRef(); + break; + case SUBTOTAL_FUNC_SUM: + case SUBTOTAL_FUNC_MAX: + case SUBTOTAL_FUNC_MIN: + // Note that nVal is 0.0 for MAX and MIN if nCount==0, that's also + // how it is defined in ODFF. + fRet = getValueRef(); + break; + case SUBTOTAL_FUNC_PROD: + fRet = (getCountRef() > 0) ? getValueRef() : 0.0; + break; + case SUBTOTAL_FUNC_AVE: + if (getCountRef() == 0) + mbError = true; + else + fRet = getValueRef() / getCountRef(); + break; + case SUBTOTAL_FUNC_VAR: + case SUBTOTAL_FUNC_STD: + if (maWelford.getCount() < 2) + mbError = true; + else + { + fRet = maWelford.getVarianceSample(); + if (fRet < 0.0) + mbError = true; + else if (meFunc == SUBTOTAL_FUNC_STD) + fRet = sqrt(fRet); + } + break; + case SUBTOTAL_FUNC_VARP: + case SUBTOTAL_FUNC_STDP: + if (maWelford.getCount() < 1) + mbError = true; + else if (maWelford.getCount() == 1) + fRet = 0.0; + else + { + fRet = maWelford.getVariancePopulation(); + if (fRet < 0.0) + mbError = true; + else if (meFunc == SUBTOTAL_FUNC_STDP) + fRet = sqrt(fRet); + } + break; + default: + assert(!"unhandled unknown"); + mbError = true; + break; + } + if (mbError) + fRet = 0.0; + return fRet; +} + +void WelfordRunner::update(double fVal) +{ + ++mnCount; + const double fDelta = fVal - mfMean; + mfMean += fDelta / mnCount; + mfM2 += fDelta * (fVal - mfMean); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/token.cxx b/sc/source/core/tool/token.cxx new file mode 100644 index 0000000000..25c5b6f05f --- /dev/null +++ b/sc/source/core/tool/token.cxx @@ -0,0 +1,5413 @@ +/* -*- 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 <functional> + +#include <string.h> +#include <osl/diagnose.h> +#include <sal/log.hxx> + +#include <token.hxx> +#include <tokenarray.hxx> +#include <reftokenhelper.hxx> +#include <clipparam.hxx> +#include <compiler.hxx> +#include <interpre.hxx> +#include <formula/FormulaCompiler.hxx> +#include <formula/compiler.hxx> +#include <formula/opcode.hxx> +#include <jumpmatrix.hxx> +#include <rangeseq.hxx> +#include <rangeutl.hxx> +#include <externalrefmgr.hxx> +#include <document.hxx> +#include <refupdatecontext.hxx> +#include <tokenstringcontext.hxx> +#include <types.hxx> +#include <addincol.hxx> +#include <dbdata.hxx> +#include <reordermap.hxx> +#include <svl/sharedstring.hxx> +#include <scmatrix.hxx> + +#include <com/sun/star/sheet/ComplexReference.hpp> +#include <com/sun/star/sheet/ExternalReference.hpp> +#include <com/sun/star/sheet/FormulaToken.hpp> +#include <com/sun/star/sheet/ReferenceFlags.hpp> +#include <com/sun/star/sheet/NameToken.hpp> +#include <utility> +#include <o3tl/safeint.hxx> +#include <o3tl/sorted_vector.hxx> + +using ::std::vector; +using namespace formula; +using namespace com::sun::star; + +namespace +{ + void lcl_SingleRefToCalc( ScSingleRefData& rRef, const sheet::SingleReference& rAPI ) + { + rRef.InitFlags(); + + rRef.SetColRel( ( rAPI.Flags & sheet::ReferenceFlags::COLUMN_RELATIVE ) != 0 ); + rRef.SetRowRel( ( rAPI.Flags & sheet::ReferenceFlags::ROW_RELATIVE ) != 0 ); + rRef.SetTabRel( ( rAPI.Flags & sheet::ReferenceFlags::SHEET_RELATIVE ) != 0 ); + rRef.SetColDeleted( ( rAPI.Flags & sheet::ReferenceFlags::COLUMN_DELETED ) != 0 ); + rRef.SetRowDeleted( ( rAPI.Flags & sheet::ReferenceFlags::ROW_DELETED ) != 0 ); + rRef.SetTabDeleted( ( rAPI.Flags & sheet::ReferenceFlags::SHEET_DELETED ) != 0 ); + rRef.SetFlag3D( ( rAPI.Flags & sheet::ReferenceFlags::SHEET_3D ) != 0 ); + rRef.SetRelName( ( rAPI.Flags & sheet::ReferenceFlags::RELATIVE_NAME ) != 0 ); + + if (rRef.IsColRel()) + rRef.SetRelCol(static_cast<SCCOL>(rAPI.RelativeColumn)); + else + rRef.SetAbsCol(static_cast<SCCOL>(rAPI.Column)); + + if (rRef.IsRowRel()) + rRef.SetRelRow(static_cast<SCROW>(rAPI.RelativeRow)); + else + rRef.SetAbsRow(static_cast<SCROW>(rAPI.Row)); + + if (rRef.IsTabRel()) + rRef.SetRelTab(static_cast<SCTAB>(rAPI.RelativeSheet)); + else + rRef.SetAbsTab(static_cast<SCTAB>(rAPI.Sheet)); + } + + void lcl_ExternalRefToCalc( ScSingleRefData& rRef, const sheet::SingleReference& rAPI ) + { + rRef.InitFlags(); + + rRef.SetColRel( ( rAPI.Flags & sheet::ReferenceFlags::COLUMN_RELATIVE ) != 0 ); + rRef.SetRowRel( ( rAPI.Flags & sheet::ReferenceFlags::ROW_RELATIVE ) != 0 ); + rRef.SetColDeleted( ( rAPI.Flags & sheet::ReferenceFlags::COLUMN_DELETED ) != 0 ); + rRef.SetRowDeleted( ( rAPI.Flags & sheet::ReferenceFlags::ROW_DELETED ) != 0 ); + rRef.SetTabDeleted( false ); // sheet must not be deleted for external refs + rRef.SetFlag3D( ( rAPI.Flags & sheet::ReferenceFlags::SHEET_3D ) != 0 ); + rRef.SetRelName( false ); + + if (rRef.IsColRel()) + rRef.SetRelCol(static_cast<SCCOL>(rAPI.RelativeColumn)); + else + rRef.SetAbsCol(static_cast<SCCOL>(rAPI.Column)); + + if (rRef.IsRowRel()) + rRef.SetRelRow(static_cast<SCROW>(rAPI.RelativeRow)); + else + rRef.SetAbsRow(static_cast<SCROW>(rAPI.Row)); + + // sheet index must be absolute for external refs + rRef.SetAbsTab(0); + } + + struct TokenPointerRange + { + FormulaToken** mpStart; + FormulaToken** mpStop; + + TokenPointerRange() : mpStart(nullptr), mpStop(nullptr) {} + TokenPointerRange( FormulaToken** p, sal_uInt16 n ) : + mpStart(p), mpStop( p + static_cast<size_t>(n)) {} + }; + struct TokenPointers + { + TokenPointerRange maPointerRange[2]; + bool mbSkipRelName; + + TokenPointers( FormulaToken** pCode, sal_uInt16 nLen, FormulaToken** pRPN, sal_uInt16 nRPN, + bool bSkipRelName = true ) : + mbSkipRelName(bSkipRelName) + { + maPointerRange[0] = TokenPointerRange( pCode, nLen); + maPointerRange[1] = TokenPointerRange( pRPN, nRPN); + } + + bool skipToken( size_t i, const FormulaToken* const * pp ) + { + // Handle all code tokens, and tokens in RPN only if they have a + // reference count of 1, which means they are not referenced in the + // code array. Doing it the other way would skip code tokens that + // are held by flat copied token arrays and thus are shared. For + // flat copy arrays the caller has to know what it does and should + // discard all RPN, update only one array and regenerate all RPN. + if (i == 1) + { + if ((*pp)->GetRef() > 1) + return true; + + if (mbSkipRelName) + { + // Skip (do not adjust) relative references resulting from + // named expressions. Resolved expressions are only in RPN. + switch ((*pp)->GetType()) + { + case svSingleRef: + return (*pp)->GetSingleRef()->IsRelName(); + case svDoubleRef: + { + const ScComplexRefData& rRef = *(*pp)->GetDoubleRef(); + return rRef.Ref1.IsRelName() || rRef.Ref2.IsRelName(); + } + default: + ; // nothing + } + } + } + + return false; + } + + FormulaToken* getHandledToken( size_t i, FormulaToken* const * pp ) + { + if (skipToken( i, pp)) + return nullptr; + + FormulaToken* p = *pp; + if (p->GetOpCode() == ocTableRef) + { + // Return the inner reference token if it is not in RPN. + ScTableRefToken* pTR = dynamic_cast<ScTableRefToken*>(p); + if (!pTR) + return p; + p = pTR->GetAreaRefRPN(); + if (!p) + return pTR; + if (p->GetRef() > 1) + // Reference handled in RPN, but do not return nullptr so + // loops will process ocTableRef via pp instead of issuing + // a continue. + return pTR; + } + return p; + } + }; + +} // namespace + + +// --- class ScRawToken ----------------------------------------------------- + +void ScRawToken::SetOpCode( OpCode e ) +{ + eOp = e; + switch (eOp) + { + case ocIf: + eType = svJump; + nJump[ 0 ] = 3; // If, Else, Behind + break; + case ocIfError: + case ocIfNA: + eType = svJump; + nJump[ 0 ] = 2; // If, Behind + break; + case ocChoose: + eType = svJump; + nJump[ 0 ] = FORMULA_MAXJUMPCOUNT + 1; + break; + case ocMissing: + eType = svMissing; + break; + case ocSep: + case ocOpen: + case ocClose: + case ocArrayRowSep: + case ocArrayColSep: + case ocArrayOpen: + case ocArrayClose: + case ocTableRefOpen: + case ocTableRefClose: + eType = svSep; + break; + case ocWhitespace: + eType = svByte; + whitespace.nCount = 1; + whitespace.cChar = 0x20; + break; + default: + eType = svByte; + sbyte.cByte = 0; + sbyte.eInForceArray = ParamClass::Unknown; + } +} + +void ScRawToken::SetString( rtl_uString* pData, rtl_uString* pDataIgnoreCase ) +{ + eOp = ocPush; + eType = svString; + + sharedstring.mpData = pData; + sharedstring.mpDataIgnoreCase = pDataIgnoreCase; +} + +void ScRawToken::SetSingleReference( const ScSingleRefData& rRef ) +{ + eOp = ocPush; + eType = svSingleRef; + aRef.Ref1 = + aRef.Ref2 = rRef; +} + +void ScRawToken::SetDoubleReference( const ScComplexRefData& rRef ) +{ + eOp = ocPush; + eType = svDoubleRef; + aRef = rRef; +} + +void ScRawToken::SetDouble(double rVal) +{ + eOp = ocPush; + eType = svDouble; + nValue = rVal; +} + +void ScRawToken::SetErrorConstant( FormulaError nErr ) +{ + eOp = ocPush; + eType = svError; + nError = nErr; +} + +void ScRawToken::SetName(sal_Int16 nSheet, sal_uInt16 nIndex) +{ + eOp = ocName; + eType = svIndex; + + name.nSheet = nSheet; + name.nIndex = nIndex; +} + +void ScRawToken::SetExternalSingleRef( sal_uInt16 nFileId, const OUString& rTabName, const ScSingleRefData& rRef ) +{ + eOp = ocPush; + eType = svExternalSingleRef; + + extref.nFileId = nFileId; + extref.aRef.Ref1 = + extref.aRef.Ref2 = rRef; + maExternalName = rTabName; +} + +void ScRawToken::SetExternalDoubleRef( sal_uInt16 nFileId, const OUString& rTabName, const ScComplexRefData& rRef ) +{ + eOp = ocPush; + eType = svExternalDoubleRef; + + extref.nFileId = nFileId; + extref.aRef = rRef; + maExternalName = rTabName; +} + +void ScRawToken::SetExternalName( sal_uInt16 nFileId, const OUString& rName ) +{ + eOp = ocPush; + eType = svExternalName; + + extname.nFileId = nFileId; + maExternalName = rName; +} + +void ScRawToken::SetExternal( const OUString& rStr ) +{ + eOp = ocExternal; + eType = svExternal; + maExternalName = rStr; +} + +bool ScRawToken::IsValidReference(const ScDocument& rDoc) const +{ + switch (eType) + { + case svSingleRef: + return aRef.Ref1.Valid(rDoc); + case svDoubleRef: + return aRef.Valid(rDoc); + case svExternalSingleRef: + case svExternalDoubleRef: + return true; + default: + ; // nothing + } + return false; +} + +FormulaToken* ScRawToken::CreateToken(ScSheetLimits& rLimits) const +{ +#define IF_NOT_OPCODE_ERROR(o,c) SAL_WARN_IF((eOp!=o), "sc.core", #c "::ctor: OpCode " << static_cast<int>(eOp) << " lost, converted to " #o "; maybe inherit from FormulaToken instead!") + switch ( GetType() ) + { + case svByte : + if (eOp == ocWhitespace) + return new FormulaSpaceToken( whitespace.nCount, whitespace.cChar ); + else + return new FormulaByteToken( eOp, sbyte.cByte, sbyte.eInForceArray ); + case svDouble : + IF_NOT_OPCODE_ERROR( ocPush, FormulaDoubleToken); + return new FormulaDoubleToken( nValue ); + case svString : + { + svl::SharedString aSS(sharedstring.mpData, sharedstring.mpDataIgnoreCase); + if (eOp == ocPush) + return new FormulaStringToken(std::move(aSS)); + else + return new FormulaStringOpToken(eOp, std::move(aSS)); + } + case svSingleRef : + if (eOp == ocPush) + return new ScSingleRefToken(rLimits, aRef.Ref1 ); + else + return new ScSingleRefToken(rLimits, aRef.Ref1, eOp ); + case svDoubleRef : + if (eOp == ocPush) + return new ScDoubleRefToken(rLimits, aRef ); + else + return new ScDoubleRefToken(rLimits, aRef, eOp ); + case svMatrix : + IF_NOT_OPCODE_ERROR( ocPush, ScMatrixToken); + return new ScMatrixToken( pMat ); + case svIndex : + if (eOp == ocTableRef) + return new ScTableRefToken( table.nIndex, table.eItem); + else + return new FormulaIndexToken( eOp, name.nIndex, name.nSheet); + case svExternalSingleRef: + { + svl::SharedString aTabName(maExternalName); // string not interned + return new ScExternalSingleRefToken(extref.nFileId, std::move(aTabName), extref.aRef.Ref1); + } + case svExternalDoubleRef: + { + svl::SharedString aTabName(maExternalName); // string not interned + return new ScExternalDoubleRefToken(extref.nFileId, std::move(aTabName), extref.aRef); + } + case svExternalName: + { + svl::SharedString aName(maExternalName); // string not interned + return new ScExternalNameToken( extname.nFileId, std::move(aName) ); + } + case svJump : + return new FormulaJumpToken( eOp, nJump ); + case svExternal : + return new FormulaExternalToken( eOp, sbyte.cByte, maExternalName ); + case svFAP : + return new FormulaFAPToken( eOp, sbyte.cByte, nullptr ); + case svMissing : + IF_NOT_OPCODE_ERROR( ocMissing, FormulaMissingToken); + return new FormulaMissingToken; + case svSep : + return new FormulaToken( svSep,eOp ); + case svError : + return new FormulaErrorToken( nError ); + case svUnknown : + return new FormulaUnknownToken( eOp ); + default: + { + SAL_WARN("sc.core", "unknown ScRawToken::CreateToken() type " << int(GetType())); + return new FormulaUnknownToken( ocBad ); + } + } +#undef IF_NOT_OPCODE_ERROR +} + +namespace { + +// TextEqual: if same formula entered (for optimization in sort) +bool checkTextEqual( const ScSheetLimits& rLimits, const FormulaToken& _rToken1, const FormulaToken& _rToken2 ) +{ + assert( + (_rToken1.GetType() == svSingleRef || _rToken1.GetType() == svDoubleRef) + && _rToken1.FormulaToken::operator ==(_rToken2)); + + // in relative Refs only compare relative parts + + ScComplexRefData aTemp1; + if ( _rToken1.GetType() == svSingleRef ) + { + aTemp1.Ref1 = *_rToken1.GetSingleRef(); + aTemp1.Ref2 = aTemp1.Ref1; + } + else + aTemp1 = *_rToken1.GetDoubleRef(); + + ScComplexRefData aTemp2; + if ( _rToken2.GetType() == svSingleRef ) + { + aTemp2.Ref1 = *_rToken2.GetSingleRef(); + aTemp2.Ref2 = aTemp2.Ref1; + } + else + aTemp2 = *_rToken2.GetDoubleRef(); + + ScAddress aPos; + ScRange aRange1 = aTemp1.toAbs(rLimits, aPos), aRange2 = aTemp2.toAbs(rLimits, aPos); + + // memcmp doesn't work because of the alignment byte after bFlags. + // After SmartRelAbs only absolute parts have to be compared. + return aRange1 == aRange2 && aTemp1.Ref1.FlagValue() == aTemp2.Ref1.FlagValue() && aTemp1.Ref2.FlagValue() == aTemp2.Ref2.FlagValue(); +} + +} + +#if DEBUG_FORMULA_COMPILER +void DumpToken(formula::FormulaToken const & rToken) +{ + switch (rToken.GetType()) { + case svSingleRef: + cout << "-- ScSingleRefToken" << endl; + rToken.GetSingleRef()->Dump(1); + break; + case svDoubleRef: + cout << "-- ScDoubleRefToken" << endl; + rToken.GetDoubleRef()->Dump(1); + break; + default: + cout << "-- FormulaToken" << endl; + cout << " opcode: " << int(rToken.GetOpCode()) << " " << + formula::FormulaCompiler::GetNativeSymbol( rToken.GetOpCode()).toUtf8().getStr() << endl; + cout << " type: " << static_cast<int>(rToken.GetType()) << endl; + switch (rToken.GetType()) + { + case svDouble: + cout << " value: " << rToken.GetDouble() << endl; + break; + case svString: + cout << " string: " + << OUStringToOString(rToken.GetString().getString(), RTL_TEXTENCODING_UTF8).getStr() + << endl; + break; + default: + ; + } + break; + } +} +#endif + +FormulaTokenRef extendRangeReference( ScSheetLimits& rLimits, FormulaToken & rTok1, FormulaToken & rTok2, + const ScAddress & rPos, bool bReuseDoubleRef ) +{ + + StackVar sv1 = rTok1.GetType(); + // Doing a RangeOp with RefList is probably utter nonsense, but Xcl + // supports it, so do we. + if (sv1 != svSingleRef && sv1 != svDoubleRef && sv1 != svRefList + && sv1 != svExternalSingleRef && sv1 != svExternalDoubleRef) + return nullptr; + StackVar sv2 = rTok2.GetType(); + if (sv2 != svSingleRef && sv2 != svDoubleRef && sv2 != svRefList) + return nullptr; + + ScTokenRef xRes; + bool bExternal = (sv1 == svExternalSingleRef); + if ((sv1 == svSingleRef || bExternal) && sv2 == svSingleRef) + { + // Range references like Sheet1.A1:A2 are generalized and built by + // first creating a DoubleRef from the first SingleRef, effectively + // generating Sheet1.A1:A1, and then extending that with A2 as if + // Sheet1.A1:A1:A2 was encountered, so the mechanisms to adjust the + // references apply as well. + + /* Given the current structure of external references an external + * reference can only be extended if the second reference does not + * point to a different sheet. 'file'#Sheet1.A1:A2 is ok, + * 'file'#Sheet1.A1:Sheet2.A2 is not. Since we can't determine from a + * svSingleRef whether the sheet would be different from the one given + * in the external reference, we have to bail out if there is any sheet + * specified. NOTE: Xcl does handle external 3D references as in + * '[file]Sheet1:Sheet2'!A1:A2 + * + * FIXME: For OOo syntax be smart and remember an external singleref + * encountered and if followed by ocRange and singleref, create an + * external singleref for the second singleref. Both could then be + * merged here. For Xcl syntax already parse an external range + * reference entirely, cumbersome. */ + + const ScSingleRefData& rRef2 = *rTok2.GetSingleRef(); + if (bExternal && rRef2.IsFlag3D()) + return nullptr; + + ScComplexRefData aRef; + aRef.Ref1 = aRef.Ref2 = *rTok1.GetSingleRef(); + aRef.Ref2.SetFlag3D( false); + aRef.Extend(rLimits, rRef2, rPos); + if (bExternal) + xRes = new ScExternalDoubleRefToken( rTok1.GetIndex(), rTok1.GetString(), aRef); + else + xRes = new ScDoubleRefToken(rLimits, aRef); + } + else + { + bExternal |= (sv1 == svExternalDoubleRef); + const ScRefList* pRefList = nullptr; + if (sv1 == svDoubleRef) + { + xRes = (bReuseDoubleRef && rTok1.GetRef() == 1 ? &rTok1 : rTok1.Clone()); + sv1 = svUnknown; // mark as handled + } + else if (sv2 == svDoubleRef) + { + xRes = (bReuseDoubleRef && rTok2.GetRef() == 1 ? &rTok2 : rTok2.Clone()); + sv2 = svUnknown; // mark as handled + } + else if (sv1 == svRefList) + pRefList = rTok1.GetRefList(); + else if (sv2 == svRefList) + pRefList = rTok2.GetRefList(); + if (pRefList) + { + if (pRefList->empty()) + return nullptr; + if (bExternal) + return nullptr; // external reference list not possible + xRes = new ScDoubleRefToken(rLimits, (*pRefList)[0] ); + } + if (!xRes) + return nullptr; // shouldn't happen... + StackVar sv[2] = { sv1, sv2 }; + formula::FormulaToken* pt[2] = { &rTok1, &rTok2 }; + ScComplexRefData& rRef = *xRes->GetDoubleRef(); + for (size_t i=0; i<2; ++i) + { + switch (sv[i]) + { + case svSingleRef: + rRef.Extend(rLimits, *pt[i]->GetSingleRef(), rPos); + break; + case svDoubleRef: + rRef.Extend(rLimits, *pt[i]->GetDoubleRef(), rPos); + break; + case svRefList: + { + const ScRefList* p = pt[i]->GetRefList(); + if (p->empty()) + return nullptr; + for (const auto& rRefData : *p) + { + rRef.Extend(rLimits, rRefData, rPos); + } + } + break; + case svExternalSingleRef: + if (rRef.Ref1.IsFlag3D() || rRef.Ref2.IsFlag3D()) + return nullptr; // no other sheets with external refs + else + rRef.Extend(rLimits, *pt[i]->GetSingleRef(), rPos); + break; + case svExternalDoubleRef: + if (rRef.Ref1.IsFlag3D() || rRef.Ref2.IsFlag3D()) + return nullptr; // no other sheets with external refs + else + rRef.Extend(rLimits, *pt[i]->GetDoubleRef(), rPos); + break; + default: + ; // nothing, prevent compiler warning + } + } + } + return FormulaTokenRef(xRes.get()); +} + +// real implementations of virtual functions + +const ScSingleRefData* ScSingleRefToken::GetSingleRef() const { return &aSingleRef; } +ScSingleRefData* ScSingleRefToken::GetSingleRef() { return &aSingleRef; } +bool ScSingleRefToken::TextEqual( const FormulaToken& _rToken ) const +{ + return FormulaToken::operator ==(_rToken) && checkTextEqual(mrSheetLimits, *this, _rToken); +} +bool ScSingleRefToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==( r ) && aSingleRef == *r.GetSingleRef(); +} + +const ScSingleRefData* ScDoubleRefToken::GetSingleRef() const { return &aDoubleRef.Ref1; } +ScSingleRefData* ScDoubleRefToken::GetSingleRef() { return &aDoubleRef.Ref1; } +const ScComplexRefData* ScDoubleRefToken::GetDoubleRef() const { return &aDoubleRef; } +ScComplexRefData* ScDoubleRefToken::GetDoubleRef() { return &aDoubleRef; } +const ScSingleRefData* ScDoubleRefToken::GetSingleRef2() const { return &aDoubleRef.Ref2; } +ScSingleRefData* ScDoubleRefToken::GetSingleRef2() { return &aDoubleRef.Ref2; } +bool ScDoubleRefToken::TextEqual( const FormulaToken& _rToken ) const +{ + return FormulaToken::operator ==(_rToken) && checkTextEqual(mrSheetLimits, *this, _rToken); +} +bool ScDoubleRefToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==( r ) && aDoubleRef == *r.GetDoubleRef(); +} + +const ScRefList* ScRefListToken::GetRefList() const { return &aRefList; } + ScRefList* ScRefListToken::GetRefList() { return &aRefList; } + bool ScRefListToken::IsArrayResult() const { return mbArrayResult; } +bool ScRefListToken::operator==( const FormulaToken& r ) const +{ + if (!FormulaToken::operator==( r ) || &aRefList != r.GetRefList()) + return false; + const ScRefListToken* p = dynamic_cast<const ScRefListToken*>(&r); + return p && mbArrayResult == p->IsArrayResult(); +} + +ScMatrixToken::ScMatrixToken( ScMatrixRef p ) : + FormulaToken(formula::svMatrix), pMatrix(std::move(p)) {} + +ScMatrixToken::ScMatrixToken( const ScMatrixToken& ) = default; + +const ScMatrix* ScMatrixToken::GetMatrix() const { return pMatrix.get(); } +ScMatrix* ScMatrixToken::GetMatrix() { return pMatrix.get(); } +bool ScMatrixToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==( r ) && pMatrix == r.GetMatrix(); +} + +ScMatrixRangeToken::ScMatrixRangeToken( const sc::RangeMatrix& rMat ) : + FormulaToken(formula::svMatrix), mpMatrix(rMat.mpMat) +{ + maRef.InitRange(rMat.mnCol1, rMat.mnRow1, rMat.mnTab1, rMat.mnCol2, rMat.mnRow2, rMat.mnTab2); +} + +ScMatrixRangeToken::ScMatrixRangeToken( const ScMatrixRangeToken& ) = default; + +sal_uInt8 ScMatrixRangeToken::GetByte() const +{ + return MATRIX_TOKEN_HAS_RANGE; +} + +const ScMatrix* ScMatrixRangeToken::GetMatrix() const +{ + return mpMatrix.get(); +} + +ScMatrix* ScMatrixRangeToken::GetMatrix() +{ + return mpMatrix.get(); +} + +const ScComplexRefData* ScMatrixRangeToken::GetDoubleRef() const +{ + return &maRef; +} + +ScComplexRefData* ScMatrixRangeToken::GetDoubleRef() +{ + return &maRef; +} + +bool ScMatrixRangeToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==(r) && mpMatrix == r.GetMatrix(); +} + +FormulaToken* ScMatrixRangeToken::Clone() const +{ + return new ScMatrixRangeToken(*this); +} + +ScExternalSingleRefToken::ScExternalSingleRefToken( sal_uInt16 nFileId, svl::SharedString aTabName, const ScSingleRefData& r ) : + FormulaToken( svExternalSingleRef, ocPush), + mnFileId(nFileId), + maTabName(std::move(aTabName)), + maSingleRef(r) +{ +} + +ScExternalSingleRefToken::~ScExternalSingleRefToken() +{ +} + +sal_uInt16 ScExternalSingleRefToken::GetIndex() const +{ + return mnFileId; +} + +const svl::SharedString & ScExternalSingleRefToken::GetString() const +{ + return maTabName; +} + +const ScSingleRefData* ScExternalSingleRefToken::GetSingleRef() const +{ + return &maSingleRef; +} + +ScSingleRefData* ScExternalSingleRefToken::GetSingleRef() +{ + return &maSingleRef; +} + +bool ScExternalSingleRefToken::operator ==( const FormulaToken& r ) const +{ + if (!FormulaToken::operator==(r)) + return false; + + if (mnFileId != r.GetIndex()) + return false; + + if (maTabName != r.GetString()) + return false; + + return maSingleRef == *r.GetSingleRef(); +} + +ScExternalDoubleRefToken::ScExternalDoubleRefToken( sal_uInt16 nFileId, svl::SharedString aTabName, const ScComplexRefData& r ) : + FormulaToken( svExternalDoubleRef, ocPush), + mnFileId(nFileId), + maTabName(std::move(aTabName)), + maDoubleRef(r) +{ +} + +ScExternalDoubleRefToken::~ScExternalDoubleRefToken() +{ +} + +sal_uInt16 ScExternalDoubleRefToken::GetIndex() const +{ + return mnFileId; +} + +const svl::SharedString & ScExternalDoubleRefToken::GetString() const +{ + return maTabName; +} + +const ScSingleRefData* ScExternalDoubleRefToken::GetSingleRef() const +{ + return &maDoubleRef.Ref1; +} + +ScSingleRefData* ScExternalDoubleRefToken::GetSingleRef() +{ + return &maDoubleRef.Ref1; +} + +const ScSingleRefData* ScExternalDoubleRefToken::GetSingleRef2() const +{ + return &maDoubleRef.Ref2; +} + +ScSingleRefData* ScExternalDoubleRefToken::GetSingleRef2() +{ + return &maDoubleRef.Ref2; +} + +const ScComplexRefData* ScExternalDoubleRefToken::GetDoubleRef() const +{ + return &maDoubleRef; +} + +ScComplexRefData* ScExternalDoubleRefToken::GetDoubleRef() +{ + return &maDoubleRef; +} + +bool ScExternalDoubleRefToken::operator ==( const FormulaToken& r ) const +{ + if (!FormulaToken::operator==(r)) + return false; + + if (mnFileId != r.GetIndex()) + return false; + + if (maTabName != r.GetString()) + return false; + + return maDoubleRef == *r.GetDoubleRef(); +} + +ScExternalNameToken::ScExternalNameToken( sal_uInt16 nFileId, svl::SharedString aName ) : + FormulaToken( svExternalName, ocPush), + mnFileId(nFileId), + maName(std::move(aName)) +{ +} + +ScExternalNameToken::~ScExternalNameToken() {} + +sal_uInt16 ScExternalNameToken::GetIndex() const +{ + return mnFileId; +} + +const svl::SharedString & ScExternalNameToken::GetString() const +{ + return maName; +} + +bool ScExternalNameToken::operator==( const FormulaToken& r ) const +{ + if ( !FormulaToken::operator==(r) ) + return false; + + if (mnFileId != r.GetIndex()) + return false; + + return maName.getData() == r.GetString().getData(); +} + +ScTableRefToken::ScTableRefToken( sal_uInt16 nIndex, ScTableRefToken::Item eItem ) : + FormulaToken( svIndex, ocTableRef), + mnIndex(nIndex), + meItem(eItem) +{ +} + +ScTableRefToken::ScTableRefToken( const ScTableRefToken& r ) : + FormulaToken(r), + mxAreaRefRPN( r.mxAreaRefRPN ? r.mxAreaRefRPN->Clone() : nullptr), + mnIndex(r.mnIndex), + meItem(r.meItem) +{ +} + +ScTableRefToken::~ScTableRefToken() {} + +sal_uInt16 ScTableRefToken::GetIndex() const +{ + return mnIndex; +} + +void ScTableRefToken::SetIndex( sal_uInt16 n ) +{ + mnIndex = n; +} + +sal_Int16 ScTableRefToken::GetSheet() const +{ + // Code asking for this may have to be adapted as it might assume an + // svIndex token would always be ocName or ocDBArea. + SAL_WARN("sc.core","ScTableRefToken::GetSheet - maybe adapt caller to know about TableRef?"); + // Database range is always global. + return -1; +} + +ScTableRefToken::Item ScTableRefToken::GetItem() const +{ + return meItem; +} + +void ScTableRefToken::AddItem( ScTableRefToken::Item eItem ) +{ + meItem = static_cast<ScTableRefToken::Item>(meItem | eItem); +} + +void ScTableRefToken::SetAreaRefRPN( formula::FormulaToken* pToken ) +{ + mxAreaRefRPN = pToken; +} + +formula::FormulaToken* ScTableRefToken::GetAreaRefRPN() const +{ + return mxAreaRefRPN.get(); +} + +bool ScTableRefToken::operator==( const FormulaToken& r ) const +{ + if ( !FormulaToken::operator==(r) ) + return false; + + if (mnIndex != r.GetIndex()) + return false; + + const ScTableRefToken* p = dynamic_cast<const ScTableRefToken*>(&r); + if (!p) + return false; + + if (meItem != p->GetItem()) + return false; + + if (!mxAreaRefRPN && !p->mxAreaRefRPN) + ; // nothing + else if (!mxAreaRefRPN || !p->mxAreaRefRPN) + return false; + else if (!(*mxAreaRefRPN == *(p->mxAreaRefRPN))) + return false; + + return true; +} + +ScJumpMatrixToken::ScJumpMatrixToken(std::shared_ptr<ScJumpMatrix> p) + : FormulaToken(formula::svJumpMatrix) + , mpJumpMatrix(std::move(p)) +{} + +ScJumpMatrixToken::ScJumpMatrixToken( const ScJumpMatrixToken & ) = default; + +ScJumpMatrix* ScJumpMatrixToken::GetJumpMatrix() const +{ + return mpJumpMatrix.get(); +} + +bool ScJumpMatrixToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==( r ) && mpJumpMatrix.get() == r.GetJumpMatrix(); +} + +ScJumpMatrixToken::~ScJumpMatrixToken() +{ +} + +double ScEmptyCellToken::GetDouble() const { return 0.0; } + +const svl::SharedString & ScEmptyCellToken::GetString() const +{ + return svl::SharedString::getEmptyString(); +} + +bool ScEmptyCellToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==( r ) && + bInherited == static_cast< const ScEmptyCellToken & >(r).IsInherited() && + bDisplayedAsString == static_cast< const ScEmptyCellToken & >(r).IsDisplayedAsString(); +} + +ScMatrixCellResultToken::ScMatrixCellResultToken( ScConstMatrixRef pMat, const formula::FormulaToken* pUL ) : + FormulaToken(formula::svMatrixCell), xMatrix(std::move(pMat)), xUpperLeft(pUL) {} + +ScMatrixCellResultToken::ScMatrixCellResultToken( const ScMatrixCellResultToken& ) = default; + +double ScMatrixCellResultToken::GetDouble() const { return xUpperLeft->GetDouble(); } + +ScMatrixCellResultToken::~ScMatrixCellResultToken() {} + +const svl::SharedString & ScMatrixCellResultToken::GetString() const +{ + return xUpperLeft->GetString(); +} + +const ScMatrix* ScMatrixCellResultToken::GetMatrix() const { return xMatrix.get(); } +// Non-const GetMatrix() is private and unused but must be implemented to +// satisfy vtable linkage. +ScMatrix* ScMatrixCellResultToken::GetMatrix() +{ + return const_cast<ScMatrix*>(xMatrix.get()); +} + +bool ScMatrixCellResultToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==( r ) && + xUpperLeft == static_cast<const ScMatrixCellResultToken &>(r).xUpperLeft && + xMatrix == static_cast<const ScMatrixCellResultToken &>(r).xMatrix; +} + +FormulaToken* ScMatrixCellResultToken::Clone() const +{ + return new ScMatrixCellResultToken(*this); +} + +void ScMatrixCellResultToken::Assign( const ScMatrixCellResultToken & r ) +{ + xMatrix = r.xMatrix; + xUpperLeft = r.xUpperLeft; +} + +ScMatrixFormulaCellToken::ScMatrixFormulaCellToken( + SCCOL nC, SCROW nR, const ScConstMatrixRef& pMat, const formula::FormulaToken* pUL ) : + ScMatrixCellResultToken(pMat, pUL), nRows(nR), nCols(nC) +{ + CloneUpperLeftIfNecessary(); +} + +ScMatrixFormulaCellToken::ScMatrixFormulaCellToken( SCCOL nC, SCROW nR ) : + ScMatrixCellResultToken(nullptr, nullptr), nRows(nR), nCols(nC) {} + +ScMatrixFormulaCellToken::ScMatrixFormulaCellToken( const ScMatrixFormulaCellToken& r ) : + ScMatrixCellResultToken(r), nRows(r.nRows), nCols(r.nCols) +{ + CloneUpperLeftIfNecessary(); +} + +ScMatrixFormulaCellToken::~ScMatrixFormulaCellToken() {} + +bool ScMatrixFormulaCellToken::operator==( const FormulaToken& r ) const +{ + const ScMatrixFormulaCellToken* p = dynamic_cast<const ScMatrixFormulaCellToken*>(&r); + return p && ScMatrixCellResultToken::operator==( r ) && + nCols == p->nCols && nRows == p->nRows; +} + +void ScMatrixFormulaCellToken::CloneUpperLeftIfNecessary() +{ + if (xUpperLeft && xUpperLeft->GetType() == svDouble) + xUpperLeft = xUpperLeft->Clone(); +} + +void ScMatrixFormulaCellToken::Assign( const ScMatrixCellResultToken & r ) +{ + ScMatrixCellResultToken::Assign( r); + + CloneUpperLeftIfNecessary(); +} + +void ScMatrixFormulaCellToken::Assign( const formula::FormulaToken& r ) +{ + if (this == &r) + return; + const ScMatrixCellResultToken* p = dynamic_cast<const ScMatrixCellResultToken*>(&r); + if (p) + ScMatrixCellResultToken::Assign( *p); + else + { + OSL_ENSURE( r.GetType() != svMatrix, "ScMatrixFormulaCellToken::operator=: assigning ScMatrixToken to ScMatrixFormulaCellToken is not proper, use ScMatrixCellResultToken instead"); + if (r.GetType() == svMatrix) + { + xUpperLeft = nullptr; + xMatrix = r.GetMatrix(); + } + else + { + xUpperLeft = &r; + xMatrix = nullptr; + CloneUpperLeftIfNecessary(); + } + } +} + +void ScMatrixFormulaCellToken::SetUpperLeftDouble( double f ) +{ + switch (GetUpperLeftType()) + { + case svDouble: + const_cast<FormulaToken*>(xUpperLeft.get())->GetDoubleAsReference() = f; + break; + case svString: + xUpperLeft = new FormulaDoubleToken( f); + break; + case svUnknown: + if (!xUpperLeft) + { + xUpperLeft = new FormulaDoubleToken( f); + break; + } + [[fallthrough]]; + default: + { + OSL_FAIL("ScMatrixFormulaCellToken::SetUpperLeftDouble: not modifying unhandled token type"); + } + } +} + +void ScMatrixFormulaCellToken::ResetResult() +{ + xMatrix = nullptr; + xUpperLeft = nullptr; +} + +ScHybridCellToken::ScHybridCellToken( + double f, const svl::SharedString & rStr, OUString aFormula, bool bEmptyDisplayedAsString ) : + FormulaToken( formula::svHybridCell ), + mfDouble( f ), maString( rStr ), + maFormula(std::move( aFormula )), + mbEmptyDisplayedAsString( bEmptyDisplayedAsString) +{ + // caller, make up your mind... + assert( !bEmptyDisplayedAsString || (f == 0.0 && rStr.getString().isEmpty())); +} + +double ScHybridCellToken::GetDouble() const { return mfDouble; } + +const svl::SharedString & ScHybridCellToken::GetString() const +{ + return maString; +} + +bool ScHybridCellToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==( r ) && + mfDouble == r.GetDouble() && maString == r.GetString() && + maFormula == static_cast<const ScHybridCellToken &>(r).GetFormula(); +} + +bool ScTokenArray::AddFormulaToken( + const css::sheet::FormulaToken& rToken, svl::SharedStringPool& rSPool, formula::ExternalReferenceHelper* pExtRef) +{ + bool bError = FormulaTokenArray::AddFormulaToken(rToken, rSPool, pExtRef); + if ( bError ) + { + bError = false; + const OpCode eOpCode = static_cast<OpCode>(rToken.OpCode); // assuming equal values for the moment + + const uno::TypeClass eClass = rToken.Data.getValueTypeClass(); + switch ( eClass ) + { + case uno::TypeClass_STRUCT: + { + uno::Type aType = rToken.Data.getValueType(); + if ( aType.equals( cppu::UnoType<sheet::SingleReference>::get() ) ) + { + ScSingleRefData aSingleRef; + sheet::SingleReference aApiRef; + rToken.Data >>= aApiRef; + lcl_SingleRefToCalc( aSingleRef, aApiRef ); + if ( eOpCode == ocPush ) + AddSingleReference( aSingleRef ); + else if ( eOpCode == ocColRowName ) + AddColRowName( aSingleRef ); + else + bError = true; + } + else if ( aType.equals( cppu::UnoType<sheet::ComplexReference>::get() ) ) + { + ScComplexRefData aComplRef; + sheet::ComplexReference aApiRef; + rToken.Data >>= aApiRef; + lcl_SingleRefToCalc( aComplRef.Ref1, aApiRef.Reference1 ); + lcl_SingleRefToCalc( aComplRef.Ref2, aApiRef.Reference2 ); + + if ( eOpCode == ocPush ) + AddDoubleReference( aComplRef ); + else + bError = true; + } + else if ( aType.equals( cppu::UnoType<sheet::NameToken>::get() ) ) + { + sheet::NameToken aTokenData; + rToken.Data >>= aTokenData; + if ( eOpCode == ocName ) + { + SAL_WARN_IF( aTokenData.Sheet < -1 || std::numeric_limits<sal_Int16>::max() < aTokenData.Sheet, + "sc.core", + "ScTokenArray::AddFormulaToken - NameToken.Sheet out of limits: " << aTokenData.Sheet); + sal_Int16 nSheet = static_cast<sal_Int16>(aTokenData.Sheet); + AddRangeName(aTokenData.Index, nSheet); + } + else if (eOpCode == ocDBArea) + AddDBRange(aTokenData.Index); + else if (eOpCode == ocTableRef) + bError = true; /* TODO: implementation */ + else + bError = true; + } + else if ( aType.equals( cppu::UnoType<sheet::ExternalReference>::get() ) ) + { + sheet::ExternalReference aApiExtRef; + if( (eOpCode == ocPush) && (rToken.Data >>= aApiExtRef) && (0 <= aApiExtRef.Index) && (aApiExtRef.Index <= SAL_MAX_UINT16) ) + { + sal_uInt16 nFileId = static_cast< sal_uInt16 >( aApiExtRef.Index ); + sheet::SingleReference aApiSRef; + sheet::ComplexReference aApiCRef; + OUString aName; + if( aApiExtRef.Reference >>= aApiSRef ) + { + // try to resolve cache index to sheet name + size_t nCacheId = static_cast< size_t >( aApiSRef.Sheet ); + OUString aTabName = pExtRef->getCacheTableName( nFileId, nCacheId ); + if( !aTabName.isEmpty() ) + { + ScSingleRefData aSingleRef; + // convert column/row settings, set sheet index to absolute + lcl_ExternalRefToCalc( aSingleRef, aApiSRef ); + AddExternalSingleReference( nFileId, rSPool.intern( aTabName), aSingleRef ); + } + else + bError = true; + } + else if( aApiExtRef.Reference >>= aApiCRef ) + { + // try to resolve cache index to sheet name. + size_t nCacheId = static_cast< size_t >( aApiCRef.Reference1.Sheet ); + OUString aTabName = pExtRef->getCacheTableName( nFileId, nCacheId ); + if( !aTabName.isEmpty() ) + { + ScComplexRefData aComplRef; + // convert column/row settings, set sheet index to absolute + lcl_ExternalRefToCalc( aComplRef.Ref1, aApiCRef.Reference1 ); + lcl_ExternalRefToCalc( aComplRef.Ref2, aApiCRef.Reference2 ); + // NOTE: This assumes that cached sheets are in consecutive order! + aComplRef.Ref2.SetAbsTab( + aComplRef.Ref1.Tab() + static_cast<SCTAB>(aApiCRef.Reference2.Sheet - aApiCRef.Reference1.Sheet)); + AddExternalDoubleReference( nFileId, rSPool.intern( aTabName), aComplRef ); + } + else + bError = true; + } + else if( aApiExtRef.Reference >>= aName ) + { + if( !aName.isEmpty() ) + AddExternalName( nFileId, rSPool.intern( aName) ); + else + bError = true; + } + else + bError = true; + } + else + bError = true; + } + else + bError = true; // unknown struct + } + break; + case uno::TypeClass_SEQUENCE: + { + if ( eOpCode != ocPush ) + bError = true; // not an inline array + else if (!rToken.Data.getValueType().equals( cppu::UnoType< + uno::Sequence< uno::Sequence< uno::Any >>>::get())) + bError = true; // unexpected sequence type + else + { + ScMatrixRef xMat = ScSequenceToMatrix::CreateMixedMatrix( rToken.Data); + if (xMat) + AddMatrix( xMat); + else + bError = true; + } + } + break; + default: + bError = true; + } + } + return bError; +} + +void ScTokenArray::CheckForThreading( const FormulaToken& r ) +{ +#if HAVE_CPP_CONSTINIT_SORTED_VECTOR + constinit +#endif + static const o3tl::sorted_vector<OpCode> aThreadedCalcDenyList({ + ocIndirect, + ocMacro, + ocOffset, + ocTableOp, + ocCell, + ocMatch, + ocInfo, + ocStyle, + ocDBAverage, + ocDBCount, + ocDBCount2, + ocDBGet, + ocDBMax, + ocDBMin, + ocDBProduct, + ocDBStdDev, + ocDBStdDevP, + ocDBSum, + ocDBVar, + ocDBVarP, + ocText, + ocSheet, + ocExternal, + ocDde, + ocWebservice, + ocGetPivotData + }); + + // Don't enable threading once we decided to disable it. + if (!mbThreadingEnabled) + return; + + static const bool bThreadingProhibited = std::getenv("SC_NO_THREADED_CALCULATION"); + + if (bThreadingProhibited) + { + mbThreadingEnabled = false; + return; + } + + OpCode eOp = r.GetOpCode(); + + if (aThreadedCalcDenyList.find(eOp) != aThreadedCalcDenyList.end()) + { + SAL_INFO("sc.core.formulagroup", "opcode " << formula::FormulaCompiler().GetOpCodeMap(sheet::FormulaLanguage::ENGLISH)->getSymbol(eOp) + << "(" << int(eOp) << ") disables threaded calculation of formula group"); + mbThreadingEnabled = false; + return; + } + + if (eOp != ocPush) + return; + + switch (r.GetType()) + { + case svExternalDoubleRef: + case svExternalSingleRef: + case svExternalName: + case svMatrix: + SAL_INFO("sc.core.formulagroup", "opcode ocPush: variable type " << StackVarEnumToString(r.GetType()) + << " disables threaded calculation of formula group"); + mbThreadingEnabled = false; + return; + default: + break; + } +} + +void ScTokenArray::CheckToken( const FormulaToken& r ) +{ + if (mbThreadingEnabled) + CheckForThreading(r); + + if (IsFormulaVectorDisabled()) + return; // It's already disabled. No more checking needed. + + OpCode eOp = r.GetOpCode(); + + if (SC_OPCODE_START_FUNCTION <= eOp && eOp < SC_OPCODE_STOP_FUNCTION) + { + if (ScInterpreter::GetGlobalConfig().mbOpenCLSubsetOnly && + ScInterpreter::GetGlobalConfig().mpOpenCLSubsetOpCodes->find(eOp) == ScInterpreter::GetGlobalConfig().mpOpenCLSubsetOpCodes->end()) + { + SAL_INFO("sc.opencl", "opcode " << formula::FormulaCompiler().GetOpCodeMap(sheet::FormulaLanguage::ENGLISH)->getSymbol(eOp) + << "(" << int(eOp) << ") disables vectorisation for formula group"); + meVectorState = FormulaVectorDisabledNotInSubSet; + mbOpenCLEnabled = false; + return; + } + + // We support vectorization for the following opcodes. + switch (eOp) + { + case ocAverage: + case ocMin: + case ocMinA: + case ocMax: + case ocMaxA: + case ocSum: + case ocSumIfs: + case ocSumProduct: + case ocCount: + case ocCount2: + case ocVLookup: + case ocSLN: + case ocIRR: + case ocMIRR: + case ocPMT: + case ocRate: + case ocRRI: + case ocPpmt: + case ocFisher: + case ocFisherInv: + case ocGamma: + case ocGammaLn: + case ocNotAvail: + case ocGauss: + case ocGeoMean: + case ocHarMean: + case ocSYD: + case ocCorrel: + case ocNegBinomVert: + case ocPearson: + case ocRSQ: + case ocCos: + case ocCosecant: + case ocCosecantHyp: + case ocISPMT: + case ocPDuration: + case ocSinHyp: + case ocAbs: + case ocPV: + case ocSin: + case ocTan: + case ocTanHyp: + case ocStandard: + case ocWeibull: + case ocMedian: + case ocDDB: + case ocFV: + case ocVBD: + case ocKurt: + case ocNper: + case ocNormDist: + case ocArcCos: + case ocSqrt: + case ocArcCosHyp: + case ocNPV: + case ocStdNormDist: + case ocNormInv: + case ocSNormInv: + case ocPermut: + case ocPermutationA: + case ocPhi: + case ocIpmt: + case ocConfidence: + case ocIntercept: + case ocDB: + case ocLogInv: + case ocArcCot: + case ocCosHyp: + case ocCritBinom: + case ocArcCotHyp: + case ocArcSin: + case ocArcSinHyp: + case ocArcTan: + case ocArcTanHyp: + case ocBitAnd: + case ocForecast: + case ocLogNormDist: + case ocGammaDist: + case ocLn: + case ocRound: + case ocCot: + case ocCotHyp: + case ocFDist: + case ocVar: + case ocChiDist: + case ocPower: + case ocOdd: + case ocChiSqDist: + case ocChiSqInv: + case ocGammaInv: + case ocFloor: + case ocFInv: + case ocFTest: + case ocB: + case ocBetaDist: + case ocExp: + case ocLog10: + case ocExpDist: + case ocAverageIfs: + case ocCountIfs: + case ocCombinA: + case ocEven: + case ocLog: + case ocMod: + case ocTrunc: + case ocSkew: + case ocArcTan2: + case ocBitOr: + case ocBitLshift: + case ocBitRshift: + case ocBitXor: + case ocChiInv: + case ocPoissonDist: + case ocSumSQ: + case ocSkewp: + case ocBinomDist: + case ocVarP: + case ocCeil: + case ocCombin: + case ocDevSq: + case ocStDev: + case ocSlope: + case ocSTEYX: + case ocZTest: + case ocPi: + case ocRandom: + case ocProduct: + case ocHypGeomDist: + case ocSumX2MY2: + case ocSumX2DY2: + case ocBetaInv: + case ocTTest: + case ocTDist: + case ocTInv: + case ocSumXMY2: + case ocStDevP: + case ocCovar: + case ocAnd: + case ocOr: + case ocNot: + case ocXor: + case ocDBMax: + case ocDBMin: + case ocDBProduct: + case ocDBAverage: + case ocDBStdDev: + case ocDBStdDevP: + case ocDBSum: + case ocDBVar: + case ocDBVarP: + case ocAverageIf: + case ocDBCount: + case ocDBCount2: + case ocDeg: + case ocRoundUp: + case ocRoundDown: + case ocInt: + case ocRad: + case ocCountIf: + case ocIsEven: + case ocIsOdd: + case ocFact: + case ocAverageA: + case ocVarA: + case ocVarPA: + case ocStDevA: + case ocStDevPA: + case ocSecant: + case ocSecantHyp: + case ocSumIf: + case ocNegSub: + case ocAveDev: + // Don't change the state. + break; + default: + SAL_INFO("sc.opencl", "opcode " << formula::FormulaCompiler().GetOpCodeMap(sheet::FormulaLanguage::ENGLISH)->getSymbol(eOp) + << "(" << int(eOp) << ") disables vectorisation for formula group"); + meVectorState = FormulaVectorDisabledByOpCode; + mbOpenCLEnabled = false; + return; + } + } + else if (eOp == ocPush) + { + // This is a stack variable. See if this is a reference. + + switch (r.GetType()) + { + case svByte: + case svDouble: + case svString: + // Don't change the state. + break; + case svSingleRef: + case svDoubleRef: + // Depends on the reference state. + meVectorState = FormulaVectorCheckReference; + break; + case svError: + case svEmptyCell: + case svExternal: + case svExternalDoubleRef: + case svExternalName: + case svExternalSingleRef: + case svFAP: + case svHybridCell: + case svIndex: + case svJump: + case svJumpMatrix: + case svMatrix: + case svMatrixCell: + case svMissing: + case svRefList: + case svSep: + case svUnknown: + // We don't support vectorization on these. + SAL_INFO("sc.opencl", "opcode ocPush: variable type " << StackVarEnumToString(r.GetType()) << " disables vectorisation for formula group"); + meVectorState = FormulaVectorDisabledByStackVariable; + mbOpenCLEnabled = false; + return; + default: + ; + } + } + else if (SC_OPCODE_START_BIN_OP <= eOp && eOp < SC_OPCODE_STOP_UN_OP) + { + if (ScInterpreter::GetGlobalConfig().mbOpenCLSubsetOnly && + ScInterpreter::GetGlobalConfig().mpOpenCLSubsetOpCodes->find(eOp) == ScInterpreter::GetGlobalConfig().mpOpenCLSubsetOpCodes->end()) + { + SAL_INFO("sc.opencl", "opcode " << formula::FormulaCompiler().GetOpCodeMap(sheet::FormulaLanguage::ENGLISH)->getSymbol(eOp) + << "(" << int(eOp) << ") disables vectorisation for formula group"); + meVectorState = FormulaVectorDisabledNotInSubSet; + mbOpenCLEnabled = false; + return; + } + } + else + { + // All the rest, special commands, separators, error codes, ... + switch (eOp) + { + default: + // Default is off, no vectorization. + // Mentioning some specific values below to indicate why. + + case ocName: + // Named expression would need "recursive" handling of its + // token array for vector state in + // ScFormulaCell::InterpretFormulaGroup() and below. + + case ocDBArea: + // Certainly not a vectorization of the entire area... + + case ocTableRef: + // May result in a single cell or range reference, depending on + // context. + + case ocColRowName: + // The associated reference is the name cell with which to + // create the implicit intersection. + + case ocColRowNameAuto: + // Auto column/row names lead to references computed in + // interpreter. + + SAL_INFO("sc.opencl", "opcode " << formula::FormulaCompiler().GetOpCodeMap(sheet::FormulaLanguage::ENGLISH)->getSymbol(eOp) + << "(" << int(eOp) << ") disables vectorisation for formula group"); + meVectorState = FormulaVectorDisabledByOpCode; + mbOpenCLEnabled = false; + return; + + // Known good, don't change state. + case ocStop: + case ocExternal: + case ocOpen: + case ocClose: + case ocSep: + case ocArrayOpen: + case ocArrayRowSep: + case ocArrayColSep: + case ocArrayClose: + case ocMissing: + case ocBad: + case ocSpaces: + case ocWhitespace: + case ocSkip: + case ocPercentSign: + case ocErrNull: + case ocErrDivZero: + case ocErrValue: + case ocErrRef: + case ocErrName: + case ocErrNum: + case ocErrNA: + break; + case ocIf: + case ocIfError: + case ocIfNA: + case ocChoose: + // Jump commands are now supported. + break; + } + } +} + +bool ScTokenArray::ImplGetReference( ScRange& rRange, const ScAddress& rPos, bool bValidOnly ) const +{ + bool bIs = false; + if ( pCode && nLen == 1 ) + { + const FormulaToken* pToken = pCode[0]; + if ( pToken ) + { + if ( pToken->GetType() == svSingleRef ) + { + const ScSingleRefData& rRef = *static_cast<const ScSingleRefToken*>(pToken)->GetSingleRef(); + rRange.aStart = rRange.aEnd = rRef.toAbs(*mxSheetLimits, rPos); + bIs = !bValidOnly || mxSheetLimits->ValidAddress(rRange.aStart); + } + else if ( pToken->GetType() == svDoubleRef ) + { + const ScComplexRefData& rCompl = *static_cast<const ScDoubleRefToken*>(pToken)->GetDoubleRef(); + const ScSingleRefData& rRef1 = rCompl.Ref1; + const ScSingleRefData& rRef2 = rCompl.Ref2; + rRange.aStart = rRef1.toAbs(*mxSheetLimits, rPos); + rRange.aEnd = rRef2.toAbs(*mxSheetLimits, rPos); + bIs = !bValidOnly || mxSheetLimits->ValidRange(rRange); + } + } + } + return bIs; +} + +namespace { + +// we want to compare for similar not identical formulae +// so we can't use actual row & column indices. +size_t HashSingleRef( const ScSingleRefData& rRef ) +{ + size_t nVal = 0; + + nVal += size_t(rRef.IsColRel()); + nVal += (size_t(rRef.IsRowRel()) << 1); + nVal += (size_t(rRef.IsTabRel()) << 2); + + return nVal; +} + +} + +void ScTokenArray::GenHash() +{ + static const OUStringHash aHasher; + + size_t nHash = 1; + OpCode eOp; + StackVar eType; + const formula::FormulaToken* p; + sal_uInt16 n = std::min<sal_uInt16>(nLen, 20); + for (sal_uInt16 i = 0; i < n; ++i) + { + p = pCode[i]; + eOp = p->GetOpCode(); + if (eOp == ocPush) + { + // This is stack variable. Do additional differentiation. + eType = p->GetType(); + switch (eType) + { + case svByte: + { + // Constant value. + sal_uInt8 nVal = p->GetByte(); + nHash += static_cast<size_t>(nVal); + } + break; + case svDouble: + { + // Constant value. + double fVal = p->GetDouble(); + nHash += std::hash<double>()(fVal); + } + break; + case svString: + { + // Constant string. + OUString aStr = p->GetString().getString(); + nHash += aHasher(aStr); + } + break; + case svSingleRef: + { + size_t nVal = HashSingleRef(*p->GetSingleRef()); + nHash += nVal; + } + break; + case svDoubleRef: + { + const ScComplexRefData& rRef = *p->GetDoubleRef(); + size_t nVal1 = HashSingleRef(rRef.Ref1); + size_t nVal2 = HashSingleRef(rRef.Ref2); + nHash += nVal1; + nHash += nVal2; + } + break; + default: + // Use the opcode value in all the other cases. + nHash += static_cast<size_t>(eOp); + } + } + else + // Use the opcode value in all the other cases. + nHash += static_cast<size_t>(eOp); + + nHash = (nHash << 4) - nHash; + } + + mnHashValue = nHash; +} + +void ScTokenArray::ResetVectorState() +{ + mbOpenCLEnabled = ScCalcConfig::isOpenCLEnabled(); + meVectorState = mbOpenCLEnabled ? FormulaVectorEnabled : FormulaVectorDisabled; + mbThreadingEnabled = ScCalcConfig::isThreadingEnabled(); +} + +bool ScTokenArray::IsFormulaVectorDisabled() const +{ + switch (meVectorState) + { + case FormulaVectorDisabled: + case FormulaVectorDisabledByOpCode: + case FormulaVectorDisabledByStackVariable: + case FormulaVectorDisabledNotInSubSet: + return true; + default: + ; + } + + return false; +} + +bool ScTokenArray::IsInvariant() const +{ + FormulaToken** p = pCode.get(); + FormulaToken** pEnd = p + static_cast<size_t>(nLen); + for (; p != pEnd; ++p) + { + switch ((*p)->GetType()) + { + case svSingleRef: + case svExternalSingleRef: + { + const ScSingleRefData& rRef = *(*p)->GetSingleRef(); + if (rRef.IsRowRel()) + return false; + } + break; + case svDoubleRef: + case svExternalDoubleRef: + { + const ScComplexRefData& rRef = *(*p)->GetDoubleRef(); + if (rRef.Ref1.IsRowRel() || rRef.Ref2.IsRowRel()) + return false; + } + break; + case svIndex: + return false; + default: + ; + } + } + + return true; +} + +bool ScTokenArray::IsReference( ScRange& rRange, const ScAddress& rPos ) const +{ + return ImplGetReference(rRange, rPos, false); +} + +bool ScTokenArray::IsValidReference( ScRange& rRange, const ScAddress& rPos ) const +{ + return ImplGetReference(rRange, rPos, true); +} + +ScTokenArray::ScTokenArray(const ScDocument& rDoc) : + mxSheetLimits(&rDoc.GetSheetLimits()), + mnHashValue(0) +{ + ResetVectorState(); +} + +ScTokenArray::ScTokenArray(ScSheetLimits& rLimits) : + mxSheetLimits(&rLimits), + mnHashValue(0) +{ + ResetVectorState(); +} + +ScTokenArray::~ScTokenArray() +{ +} + +ScTokenArray& ScTokenArray::operator=( const ScTokenArray& rArr ) +{ + Clear(); + Assign( rArr ); + mnHashValue = rArr.mnHashValue; + meVectorState = rArr.meVectorState; + mbOpenCLEnabled = rArr.mbOpenCLEnabled; + mbThreadingEnabled = rArr.mbThreadingEnabled; + return *this; +} + +ScTokenArray& ScTokenArray::operator=( ScTokenArray&& rArr ) +{ + mxSheetLimits = std::move(rArr.mxSheetLimits); + mnHashValue = rArr.mnHashValue; + meVectorState = rArr.meVectorState; + mbOpenCLEnabled = rArr.mbOpenCLEnabled; + mbThreadingEnabled = rArr.mbThreadingEnabled; + Move(std::move(rArr)); + return *this; +} + +bool ScTokenArray::EqualTokens( const ScTokenArray* pArr2) const +{ + // We only compare the non-RPN array + if ( pArr2->nLen != nLen ) + return false; + + FormulaToken** ppToken1 = GetArray(); + FormulaToken** ppToken2 = pArr2->GetArray(); + for (sal_uInt16 i=0; i<nLen; i++) + { + if ( ppToken1[i] != ppToken2[i] && + !(*ppToken1[i] == *ppToken2[i]) ) + return false; // Difference + } + return true; // All entries are the same +} + +void ScTokenArray::Clear() +{ + mnHashValue = 0; + ResetVectorState(); + FormulaTokenArray::Clear(); +} + +std::unique_ptr<ScTokenArray> ScTokenArray::Clone() const +{ + std::unique_ptr<ScTokenArray> p(new ScTokenArray(*mxSheetLimits)); + p->nLen = nLen; + p->nRPN = nRPN; + p->nMode = nMode; + p->nError = nError; + p->bHyperLink = bHyperLink; + p->mnHashValue = mnHashValue; + p->meVectorState = meVectorState; + p->mbOpenCLEnabled = mbOpenCLEnabled; + p->mbThreadingEnabled = mbThreadingEnabled; + p->mbFromRangeName = mbFromRangeName; + p->mbShareable = mbShareable; + + FormulaToken** pp; + if( nLen ) + { + p->pCode.reset(new FormulaToken*[ nLen ]); + pp = p->pCode.get(); + memcpy( pp, pCode.get(), nLen * sizeof( formula::FormulaToken* ) ); + for( sal_uInt16 i = 0; i < nLen; i++, pp++ ) + { + *pp = (*pp)->Clone(); + (*pp)->IncRef(); + } + } + if( nRPN ) + { + pp = p->pRPN = new FormulaToken*[ nRPN ]; + memcpy( pp, pRPN, nRPN * sizeof( formula::FormulaToken* ) ); + for( sal_uInt16 i = 0; i < nRPN; i++, pp++ ) + { + FormulaToken* t = *pp; + if( t->GetRef() > 1 ) + { + FormulaToken** p2 = pCode.get(); + sal_uInt16 nIdx = 0xFFFF; + for( sal_uInt16 j = 0; j < nLen; j++, p2++ ) + { + if( *p2 == t ) + { + nIdx = j; break; + } + } + if( nIdx == 0xFFFF ) + *pp = t->Clone(); + else + *pp = p->pCode[ nIdx ]; + } + else + *pp = t->Clone(); + (*pp)->IncRef(); + } + } + return p; +} + +ScTokenArray ScTokenArray::CloneValue() const +{ + ScTokenArray aNew(*mxSheetLimits); + aNew.nLen = nLen; + aNew.nRPN = nRPN; + aNew.nMode = nMode; + aNew.nError = nError; + aNew.bHyperLink = bHyperLink; + aNew.mnHashValue = mnHashValue; + aNew.meVectorState = meVectorState; + aNew.mbOpenCLEnabled = mbOpenCLEnabled; + aNew.mbThreadingEnabled = mbThreadingEnabled; + aNew.mbFromRangeName = mbFromRangeName; + aNew.mbShareable = mbShareable; + + FormulaToken** pp; + if( nLen ) + { + aNew.pCode.reset(new FormulaToken*[ nLen ]); + pp = aNew.pCode.get(); + memcpy( pp, pCode.get(), nLen * sizeof( formula::FormulaToken* ) ); + for( sal_uInt16 i = 0; i < nLen; i++, pp++ ) + { + *pp = (*pp)->Clone(); + (*pp)->IncRef(); + } + } + if( nRPN ) + { + pp = aNew.pRPN = new FormulaToken*[ nRPN ]; + memcpy( pp, pRPN, nRPN * sizeof( formula::FormulaToken* ) ); + for( sal_uInt16 i = 0; i < nRPN; i++, pp++ ) + { + FormulaToken* t = *pp; + if( t->GetRef() > 1 ) + { + FormulaToken** p2 = pCode.get(); + sal_uInt16 nIdx = 0xFFFF; + for( sal_uInt16 j = 0; j < nLen; j++, p2++ ) + { + if( *p2 == t ) + { + nIdx = j; break; + } + } + if( nIdx == 0xFFFF ) + *pp = t->Clone(); + else + *pp = aNew.pCode[ nIdx ]; + } + else + *pp = t->Clone(); + (*pp)->IncRef(); + } + } + return aNew; +} + +FormulaToken* ScTokenArray::AddRawToken( const ScRawToken& r ) +{ + return Add( r.CreateToken(*mxSheetLimits) ); +} + +// Utility function to ensure that there is strict alternation of values and +// separators. +static bool +checkArraySep( bool & bPrevWasSep, bool bNewVal ) +{ + bool bResult = (bPrevWasSep == bNewVal); + bPrevWasSep = bNewVal; + return bResult; +} + +FormulaToken* ScTokenArray::MergeArray( ) +{ + int nCol = -1, nRow = 0; + int i, nPrevRowSep = -1, nStart = 0; + bool bPrevWasSep = false; // top of stack is ocArrayClose + FormulaToken* t; + bool bNumeric = false; // numeric value encountered in current element + + // (1) Iterate from the end to the start to find matrix dims + // and do basic validation. + for ( i = nLen ; i-- > nStart ; ) + { + t = pCode[i]; + switch ( t->GetOpCode() ) + { + case ocPush : + if( checkArraySep( bPrevWasSep, false ) ) + { + return nullptr; + } + + // no references or nested arrays + if ( t->GetType() != svDouble && t->GetType() != svString ) + { + return nullptr; + } + bNumeric = (t->GetType() == svDouble); + break; + + case ocMissing : + case ocTrue : + case ocFalse : + if( checkArraySep( bPrevWasSep, false ) ) + { + return nullptr; + } + bNumeric = false; + break; + + case ocArrayColSep : + case ocSep : + if( checkArraySep( bPrevWasSep, true ) ) + { + return nullptr; + } + bNumeric = false; + break; + + case ocArrayClose : + // not possible with the , but check just in case + // something changes in the future + if( i != (nLen-1)) + { + return nullptr; + } + + if( checkArraySep( bPrevWasSep, true ) ) + { + return nullptr; + } + + nPrevRowSep = i; + bNumeric = false; + break; + + case ocArrayOpen : + nStart = i; // stop iteration + [[fallthrough]]; // to ArrayRowSep + + case ocArrayRowSep : + if( checkArraySep( bPrevWasSep, true ) ) + { + return nullptr; + } + + if( nPrevRowSep < 0 || // missing ocArrayClose + ((nPrevRowSep - i) % 2) == 1) // no complex elements + { + return nullptr; + } + + if( nCol < 0 ) + { + nCol = (nPrevRowSep - i) / 2; + } + else if( (nPrevRowSep - i)/2 != nCol) // irregular array + { + return nullptr; + } + + nPrevRowSep = i; + nRow++; + bNumeric = false; + break; + + case ocNegSub : + case ocAdd : + // negation or unary plus must precede numeric value + if( !bNumeric ) + { + return nullptr; + } + --nPrevRowSep; // shorten this row by 1 + bNumeric = false; // one level only, no --42 + break; + + case ocSpaces : + case ocWhitespace : + // ignore spaces + --nPrevRowSep; // shorten this row by 1 + break; + + default : + // no functions or operators + return nullptr; + } + } + if( nCol <= 0 || nRow <= 0 ) + return nullptr; + + int nSign = 1; + ScMatrix* pArray = new ScMatrix(nCol, nRow, 0.0); + for ( i = nStart, nCol = 0, nRow = 0 ; i < nLen ; i++ ) + { + t = pCode[i]; + + switch ( t->GetOpCode() ) + { + case ocPush : + if ( t->GetType() == svDouble ) + { + pArray->PutDouble( t->GetDouble() * nSign, nCol, nRow ); + nSign = 1; + } + else if ( t->GetType() == svString ) + { + pArray->PutString(t->GetString(), nCol, nRow); + } + break; + + case ocMissing : + pArray->PutEmpty( nCol, nRow ); + break; + + case ocTrue : + pArray->PutBoolean( true, nCol, nRow ); + break; + + case ocFalse : + pArray->PutBoolean( false, nCol, nRow ); + break; + + case ocArrayColSep : + case ocSep : + nCol++; + break; + + case ocArrayRowSep : + nRow++; nCol = 0; + break; + + case ocNegSub : + nSign = -nSign; + break; + + default : + break; + } + pCode[i] = nullptr; + t->DecRef(); + } + nLen = sal_uInt16( nStart ); + return AddMatrix( pArray ); +} + +void ScTokenArray::MergeRangeReference( const ScAddress & rPos ) +{ + if (!pCode || !nLen) + return; + sal_uInt16 nIdx = nLen; + + // The actual types are checked in extendRangeReference(). + FormulaToken *p3 = PeekPrev(nIdx); // ref + if (!p3) + return; + FormulaToken *p2 = PeekPrev(nIdx); // ocRange + if (!p2 || p2->GetOpCode() != ocRange) + return; + FormulaToken *p1 = PeekPrev(nIdx); // ref + if (!p1) + return; + FormulaTokenRef p = extendRangeReference( *mxSheetLimits, *p1, *p3, rPos, true); + if (p) + { + p->IncRef(); + p1->DecRef(); + p2->DecRef(); + p3->DecRef(); + nLen -= 2; + pCode[ nLen-1 ] = p.get(); + } +} + +FormulaToken* ScTokenArray::AddOpCode( OpCode e ) +{ + ScRawToken t; + t.SetOpCode( e ); + return AddRawToken( t ); +} + +FormulaToken* ScTokenArray::AddSingleReference( const ScSingleRefData& rRef ) +{ + return Add( new ScSingleRefToken( *mxSheetLimits, rRef ) ); +} + +FormulaToken* ScTokenArray::AddMatrixSingleReference( const ScSingleRefData& rRef ) +{ + return Add( new ScSingleRefToken(*mxSheetLimits, rRef, ocMatRef ) ); +} + +FormulaToken* ScTokenArray::AddDoubleReference( const ScComplexRefData& rRef ) +{ + return Add( new ScDoubleRefToken(*mxSheetLimits, rRef ) ); +} + +FormulaToken* ScTokenArray::AddMatrix( const ScMatrixRef& p ) +{ + return Add( new ScMatrixToken( p ) ); +} + +void ScTokenArray::AddRangeName( sal_uInt16 n, sal_Int16 nSheet ) +{ + Add( new FormulaIndexToken( ocName, n, nSheet)); +} + +FormulaToken* ScTokenArray::AddDBRange( sal_uInt16 n ) +{ + return Add( new FormulaIndexToken( ocDBArea, n)); +} + +FormulaToken* ScTokenArray::AddExternalName( sal_uInt16 nFileId, const svl::SharedString& rName ) +{ + return Add( new ScExternalNameToken(nFileId, rName) ); +} + +void ScTokenArray::AddExternalSingleReference( sal_uInt16 nFileId, const svl::SharedString& rTabName, + const ScSingleRefData& rRef ) +{ + Add( new ScExternalSingleRefToken(nFileId, rTabName, rRef) ); +} + +FormulaToken* ScTokenArray::AddExternalDoubleReference( sal_uInt16 nFileId, const svl::SharedString& rTabName, + const ScComplexRefData& rRef ) +{ + return Add( new ScExternalDoubleRefToken(nFileId, rTabName, rRef) ); +} + +FormulaToken* ScTokenArray::AddColRowName( const ScSingleRefData& rRef ) +{ + return Add( new ScSingleRefToken(*mxSheetLimits, rRef, ocColRowName ) ); +} + +void ScTokenArray::AssignXMLString( const OUString &rText, const OUString &rFormulaNmsp ) +{ + sal_uInt16 nTokens = 1; + FormulaToken *aTokens[2]; + + aTokens[0] = new FormulaStringOpToken( ocStringXML, svl::SharedString( rText) ); // string not interned + if( !rFormulaNmsp.isEmpty() ) + aTokens[ nTokens++ ] = new FormulaStringOpToken( ocStringXML, + svl::SharedString( rFormulaNmsp) ); // string not interned + + Assign( nTokens, aTokens ); +} + +bool ScTokenArray::GetAdjacentExtendOfOuterFuncRefs( SCCOLROW& nExtend, + const ScAddress& rPos, ScDirection eDir ) +{ + SCCOL nCol = 0; + SCROW nRow = 0; + switch ( eDir ) + { + case DIR_BOTTOM : + if ( rPos.Row() >= mxSheetLimits->mnMaxRow ) + return false; + nExtend = rPos.Row(); + nRow = nExtend + 1; + break; + case DIR_RIGHT : + if ( rPos.Col() >= mxSheetLimits->mnMaxCol ) + return false; + nExtend = rPos.Col(); + nCol = static_cast<SCCOL>(nExtend) + 1; + break; + case DIR_TOP : + if ( rPos.Row() <= 0 ) + return false; + nExtend = rPos.Row(); + nRow = nExtend - 1; + break; + case DIR_LEFT : + if ( rPos.Col() <= 0 ) + return false; + nExtend = rPos.Col(); + nCol = static_cast<SCCOL>(nExtend) - 1; + break; + default: + OSL_FAIL( "unknown Direction" ); + return false; + } + if ( pRPN && nRPN ) + { + FormulaToken* t = pRPN[nRPN-1]; + if ( t->GetType() == svByte ) + { + sal_uInt8 nParamCount = t->GetByte(); + if ( nParamCount && nRPN > nParamCount ) + { + bool bRet = false; + sal_uInt16 nParam = nRPN - nParamCount - 1; + for ( ; nParam < nRPN-1; nParam++ ) + { + FormulaToken* p = pRPN[nParam]; + switch ( p->GetType() ) + { + case svSingleRef : + { + ScSingleRefData& rRef = *p->GetSingleRef(); + ScAddress aAbs = rRef.toAbs(*mxSheetLimits, rPos); + switch ( eDir ) + { + case DIR_BOTTOM : + if (aAbs.Row() == nRow && aAbs.Row() > nExtend) + { + nExtend = aAbs.Row(); + bRet = true; + } + break; + case DIR_RIGHT : + if (aAbs.Col() == nCol && static_cast<SCCOLROW>(aAbs.Col()) > nExtend) + { + nExtend = aAbs.Col(); + bRet = true; + } + break; + case DIR_TOP : + if (aAbs.Row() == nRow && aAbs.Row() < nExtend) + { + nExtend = aAbs.Row(); + bRet = true; + } + break; + case DIR_LEFT : + if (aAbs.Col() == nCol && static_cast<SCCOLROW>(aAbs.Col()) < nExtend) + { + nExtend = aAbs.Col(); + bRet = true; + } + break; + } + } + break; + case svDoubleRef : + { + ScComplexRefData& rRef = *p->GetDoubleRef(); + ScRange aAbs = rRef.toAbs(*mxSheetLimits, rPos); + switch ( eDir ) + { + case DIR_BOTTOM : + if (aAbs.aStart.Row() == nRow && aAbs.aEnd.Row() > nExtend) + { + nExtend = aAbs.aEnd.Row(); + bRet = true; + } + break; + case DIR_RIGHT : + if (aAbs.aStart.Col() == nCol && static_cast<SCCOLROW>(aAbs.aEnd.Col()) > nExtend) + { + nExtend = aAbs.aEnd.Col(); + bRet = true; + } + break; + case DIR_TOP : + if (aAbs.aEnd.Row() == nRow && aAbs.aStart.Row() < nExtend) + { + nExtend = aAbs.aStart.Row(); + bRet = true; + } + break; + case DIR_LEFT : + if (aAbs.aEnd.Col() == nCol && static_cast<SCCOLROW>(aAbs.aStart.Col()) < nExtend) + { + nExtend = aAbs.aStart.Col(); + bRet = true; + } + break; + } + } + break; + default: + { + // added to avoid warnings + } + } // switch + } // for + return bRet; + } + } + } + return false; +} + +namespace { + +void GetExternalTableData(const ScDocument* pOldDoc, const ScDocument* pNewDoc, const SCTAB nTab, OUString& rTabName, sal_uInt16& rFileId) +{ + const OUString& aFileName = pOldDoc->GetFileURL(); + rFileId = pNewDoc->GetExternalRefManager()->getExternalFileId(aFileName); + rTabName = pOldDoc->GetCopyTabName(nTab); + if (rTabName.isEmpty()) + pOldDoc->GetName(nTab, rTabName); +} + +bool IsInCopyRange( const ScRange& rRange, const ScDocument* pClipDoc ) +{ + ScClipParam& rClipParam = const_cast<ScDocument*>(pClipDoc)->GetClipParam(); + return rClipParam.maRanges.Contains(rRange); +} + +bool SkipReference(formula::FormulaToken* pToken, const ScAddress& rPos, const ScDocument& rOldDoc, bool bRangeName, bool bCheckCopyArea) +{ + ScRange aRange; + + if (!ScRefTokenHelper::getRangeFromToken(&rOldDoc, aRange, pToken, rPos)) + return true; + + if (bRangeName && aRange.aStart.Tab() == rPos.Tab()) + { + switch (pToken->GetType()) + { + case svDoubleRef: + { + ScSingleRefData& rRef = *pToken->GetSingleRef2(); + if (rRef.IsColRel() || rRef.IsRowRel()) + return true; + } + [[fallthrough]]; + case svSingleRef: + { + ScSingleRefData& rRef = *pToken->GetSingleRef(); + if (rRef.IsColRel() || rRef.IsRowRel()) + return true; + } + break; + default: + break; + } + } + + if (bCheckCopyArea && IsInCopyRange(aRange, &rOldDoc)) + return true; + + return false; +} + +void AdjustSingleRefData( ScSingleRefData& rRef, const ScAddress& rOldPos, const ScAddress& rNewPos) +{ + SCCOL nCols = rNewPos.Col() - rOldPos.Col(); + SCROW nRows = rNewPos.Row() - rOldPos.Row(); + SCTAB nTabs = rNewPos.Tab() - rOldPos.Tab(); + + if (!rRef.IsColRel()) + rRef.IncCol(nCols); + + if (!rRef.IsRowRel()) + rRef.IncRow(nRows); + + if (!rRef.IsTabRel()) + rRef.IncTab(nTabs); +} + +} + +void ScTokenArray::ReadjustAbsolute3DReferences( const ScDocument& rOldDoc, ScDocument& rNewDoc, const ScAddress& rPos, bool bRangeName ) +{ + for ( sal_uInt16 j=0; j<nLen; ++j ) + { + switch ( pCode[j]->GetType() ) + { + case svDoubleRef : + { + if (SkipReference(pCode[j], rPos, rOldDoc, bRangeName, true)) + continue; + + ScComplexRefData& rRef = *pCode[j]->GetDoubleRef(); + ScSingleRefData& rRef2 = rRef.Ref2; + ScSingleRefData& rRef1 = rRef.Ref1; + + if ( (rRef2.IsFlag3D() && !rRef2.IsTabRel()) || (rRef1.IsFlag3D() && !rRef1.IsTabRel()) ) + { + OUString aTabName; + sal_uInt16 nFileId; + GetExternalTableData(&rOldDoc, &rNewDoc, rRef1.Tab(), aTabName, nFileId); + ReplaceToken( j, new ScExternalDoubleRefToken( nFileId, + rNewDoc.GetSharedStringPool().intern( aTabName), rRef), CODE_AND_RPN); + // ATTENTION: rRef can't be used after this point + } + } + break; + case svSingleRef : + { + if (SkipReference(pCode[j], rPos, rOldDoc, bRangeName, true)) + continue; + + ScSingleRefData& rRef = *pCode[j]->GetSingleRef(); + + if ( rRef.IsFlag3D() && !rRef.IsTabRel() ) + { + OUString aTabName; + sal_uInt16 nFileId; + GetExternalTableData(&rOldDoc, &rNewDoc, rRef.Tab(), aTabName, nFileId); + ReplaceToken( j, new ScExternalSingleRefToken( nFileId, + rNewDoc.GetSharedStringPool().intern( aTabName), rRef), CODE_AND_RPN); + // ATTENTION: rRef can't be used after this point + } + } + break; + default: + { + // added to avoid warnings + } + } + } +} + +void ScTokenArray::AdjustAbsoluteRefs( const ScDocument& rOldDoc, const ScAddress& rOldPos, const ScAddress& rNewPos, + bool bCheckCopyRange) +{ + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN, true); + for (size_t j=0; j<2; ++j) + { + FormulaToken** pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken** pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch ( p->GetType() ) + { + case svDoubleRef : + { + if (!SkipReference(p, rOldPos, rOldDoc, false, bCheckCopyRange)) + continue; + + ScComplexRefData& rRef = *p->GetDoubleRef(); + ScSingleRefData& rRef2 = rRef.Ref2; + ScSingleRefData& rRef1 = rRef.Ref1; + + AdjustSingleRefData( rRef1, rOldPos, rNewPos ); + AdjustSingleRefData( rRef2, rOldPos, rNewPos ); + } + break; + case svSingleRef : + { + if (!SkipReference(p, rOldPos, rOldDoc, false, bCheckCopyRange)) + continue; + + ScSingleRefData& rRef = *p->GetSingleRef(); + + AdjustSingleRefData( rRef, rOldPos, rNewPos ); + } + break; + default: + { + // added to avoid warnings + } + } + } + } +} + +void ScTokenArray::AdjustSheetLocalNameReferences( SCTAB nOldTab, SCTAB nNewTab ) +{ + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN, false); + for (size_t j=0; j<2; ++j) + { + FormulaToken** pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken** pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch ( p->GetType() ) + { + case svDoubleRef : + { + ScComplexRefData& rRef = *p->GetDoubleRef(); + ScSingleRefData& rRef2 = rRef.Ref2; + ScSingleRefData& rRef1 = rRef.Ref1; + + if (!rRef1.IsTabRel() && rRef1.Tab() == nOldTab) + rRef1.SetAbsTab( nNewTab); + if (!rRef2.IsTabRel() && rRef2.Tab() == nOldTab) + rRef2.SetAbsTab( nNewTab); + if (!rRef1.IsTabRel() && !rRef2.IsTabRel() && rRef1.Tab() > rRef2.Tab()) + { + SCTAB nTab = rRef1.Tab(); + rRef1.SetAbsTab( rRef2.Tab()); + rRef2.SetAbsTab( nTab); + } + } + break; + case svSingleRef : + { + ScSingleRefData& rRef = *p->GetSingleRef(); + + if (!rRef.IsTabRel() && rRef.Tab() == nOldTab) + rRef.SetAbsTab( nNewTab); + } + break; + default: + ; + } + } + } +} + +bool ScTokenArray::ReferencesSheet( SCTAB nTab, SCTAB nPosTab ) const +{ + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN, false); + for (size_t j=0; j<2; ++j) + { + FormulaToken* const * pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken* const * const pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + const FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch ( p->GetType() ) + { + case svDoubleRef : + { + const ScComplexRefData& rRef = *p->GetDoubleRef(); + const ScSingleRefData& rRef2 = rRef.Ref2; + const ScSingleRefData& rRef1 = rRef.Ref1; + + SCTAB nTab1 = (rRef1.IsTabRel() ? rRef1.Tab() + nPosTab : rRef1.Tab()); + SCTAB nTab2 = (rRef2.IsTabRel() ? rRef2.Tab() + nPosTab : rRef2.Tab()); + if (nTab1 <= nTab && nTab <= nTab2) + return true; + } + break; + case svSingleRef : + { + const ScSingleRefData& rRef = *p->GetSingleRef(); + if (rRef.IsTabRel()) + { + if (rRef.Tab() + nPosTab == nTab) + return true; + } + else + { + if (rRef.Tab() == nTab) + return true; + } + } + break; + default: + ; + } + } + } + return false; +} + +namespace { + +ScRange getSelectedRange( const sc::RefUpdateContext& rCxt ) +{ + ScRange aSelectedRange(ScAddress::INITIALIZE_INVALID); + if (rCxt.mnColDelta < 0) + { + // Delete and shift to left. + aSelectedRange.aStart = ScAddress(rCxt.maRange.aStart.Col()+rCxt.mnColDelta, rCxt.maRange.aStart.Row(), rCxt.maRange.aStart.Tab()); + aSelectedRange.aEnd = ScAddress(rCxt.maRange.aStart.Col()-1, rCxt.maRange.aEnd.Row(), rCxt.maRange.aEnd.Tab()); + } + else if (rCxt.mnRowDelta < 0) + { + // Delete and shift up. + aSelectedRange.aStart = ScAddress(rCxt.maRange.aStart.Col(), rCxt.maRange.aStart.Row()+rCxt.mnRowDelta, rCxt.maRange.aStart.Tab()); + aSelectedRange.aEnd = ScAddress(rCxt.maRange.aEnd.Col(), rCxt.maRange.aStart.Row()-1, rCxt.maRange.aEnd.Tab()); + } + else if (rCxt.mnTabDelta < 0) + { + // Deleting sheets. + // TODO : Figure out what to do here. + } + else if (rCxt.mnColDelta > 0) + { + // Insert and shift to the right. + aSelectedRange.aStart = rCxt.maRange.aStart; + aSelectedRange.aEnd = ScAddress(rCxt.maRange.aStart.Col()+rCxt.mnColDelta-1, rCxt.maRange.aEnd.Row(), rCxt.maRange.aEnd.Tab()); + } + else if (rCxt.mnRowDelta > 0) + { + // Insert and shift down. + aSelectedRange.aStart = rCxt.maRange.aStart; + aSelectedRange.aEnd = ScAddress(rCxt.maRange.aEnd.Col(), rCxt.maRange.aStart.Row()+rCxt.mnRowDelta-1, rCxt.maRange.aEnd.Tab()); + } + else if (rCxt.mnTabDelta > 0) + { + // Inserting sheets. + // TODO : Figure out what to do here. + } + + return aSelectedRange; +} + +void setRefDeleted( ScSingleRefData& rRef, const sc::RefUpdateContext& rCxt ) +{ + if (rCxt.mnColDelta < 0) + rRef.SetColDeleted(true); + else if (rCxt.mnRowDelta < 0) + rRef.SetRowDeleted(true); + else if (rCxt.mnTabDelta < 0) + rRef.SetTabDeleted(true); +} + +void restoreDeletedRef( ScSingleRefData& rRef, const sc::RefUpdateContext& rCxt ) +{ + if (rCxt.mnColDelta) + { + if (rRef.IsColDeleted()) + rRef.SetColDeleted(false); + } + else if (rCxt.mnRowDelta) + { + if (rRef.IsRowDeleted()) + rRef.SetRowDeleted(false); + } + else if (rCxt.mnTabDelta) + { + if (rRef.IsTabDeleted()) + rRef.SetTabDeleted(false); + } +} + +void setRefDeleted( ScComplexRefData& rRef, const sc::RefUpdateContext& rCxt ) +{ + if (rCxt.mnColDelta < 0) + { + rRef.Ref1.SetColDeleted(true); + rRef.Ref2.SetColDeleted(true); + } + else if (rCxt.mnRowDelta < 0) + { + rRef.Ref1.SetRowDeleted(true); + rRef.Ref2.SetRowDeleted(true); + } + else if (rCxt.mnTabDelta < 0) + { + rRef.Ref1.SetTabDeleted(true); + rRef.Ref2.SetTabDeleted(true); + } +} + +void restoreDeletedRef( ScComplexRefData& rRef, const sc::RefUpdateContext& rCxt ) +{ + restoreDeletedRef(rRef.Ref1, rCxt); + restoreDeletedRef(rRef.Ref2, rCxt); +} + +enum ShrinkResult +{ + UNMODIFIED, + SHRUNK, + STICKY +}; + +ShrinkResult shrinkRange( const sc::RefUpdateContext& rCxt, ScRange& rRefRange, const ScRange& rDeletedRange, + const ScComplexRefData& rRef ) +{ + if (!rDeletedRange.Intersects(rRefRange)) + return UNMODIFIED; + + if (rCxt.mnColDelta < 0) + { + if (rRef.IsEntireRow(rCxt.mrDoc.GetSheetLimits())) + // Entire rows are not affected, columns are anchored. + return STICKY; + + // Shifting left. + if (rRefRange.aStart.Row() < rDeletedRange.aStart.Row() || rDeletedRange.aEnd.Row() < rRefRange.aEnd.Row()) + // Deleted range is only partially overlapping in vertical direction. Bail out. + return UNMODIFIED; + + if (rDeletedRange.aStart.Col() <= rRefRange.aStart.Col()) + { + if (rRefRange.aEnd.Col() <= rDeletedRange.aEnd.Col()) + { + // Reference is entirely deleted. + rRefRange.SetInvalid(); + } + else + { + // The reference range is truncated on the left. + SCCOL nOffset = rDeletedRange.aStart.Col() - rRefRange.aStart.Col(); + SCCOL nDelta = rRefRange.aStart.Col() - rDeletedRange.aEnd.Col() - 1; + rRefRange.IncEndColSticky(rCxt.mrDoc, nDelta+nOffset); + rRefRange.aStart.IncCol(nOffset); + } + } + else if (rDeletedRange.aEnd.Col() < rRefRange.aEnd.Col()) + { + if (rRefRange.IsEndColSticky(rCxt.mrDoc)) + // Sticky end not affected. + return STICKY; + + // Reference is deleted in the middle. Move the last column + // position to the left. + SCCOL nDelta = rDeletedRange.aStart.Col() - rDeletedRange.aEnd.Col() - 1; + rRefRange.IncEndColSticky(rCxt.mrDoc, nDelta); + } + else + { + if (rRefRange.IsEndColSticky(rCxt.mrDoc)) + // Sticky end not affected. + return STICKY; + + // The reference range is truncated on the right. + SCCOL nDelta = rDeletedRange.aStart.Col() - rRefRange.aEnd.Col() - 1; + rRefRange.IncEndColSticky(rCxt.mrDoc, nDelta); + } + return SHRUNK; + } + else if (rCxt.mnRowDelta < 0) + { + if (rRef.IsEntireCol(rCxt.mrDoc.GetSheetLimits())) + // Entire columns are not affected, rows are anchored. + return STICKY; + + // Shifting up. + + if (rRefRange.aStart.Col() < rDeletedRange.aStart.Col() || rDeletedRange.aEnd.Col() < rRefRange.aEnd.Col()) + // Deleted range is only partially overlapping in horizontal direction. Bail out. + return UNMODIFIED; + + if (rDeletedRange.aStart.Row() <= rRefRange.aStart.Row()) + { + if (rRefRange.aEnd.Row() <= rDeletedRange.aEnd.Row()) + { + // Reference is entirely deleted. + rRefRange.SetInvalid(); + } + else + { + // The reference range is truncated on the top. + SCROW nOffset = rDeletedRange.aStart.Row() - rRefRange.aStart.Row(); + SCROW nDelta = rRefRange.aStart.Row() - rDeletedRange.aEnd.Row() - 1; + rRefRange.IncEndRowSticky(rCxt.mrDoc, nDelta+nOffset); + rRefRange.aStart.IncRow(nOffset); + } + } + else if (rDeletedRange.aEnd.Row() < rRefRange.aEnd.Row()) + { + if (rRefRange.IsEndRowSticky(rCxt.mrDoc)) + // Sticky end not affected. + return STICKY; + + // Reference is deleted in the middle. Move the last row + // position upward. + SCROW nDelta = rDeletedRange.aStart.Row() - rDeletedRange.aEnd.Row() - 1; + rRefRange.IncEndRowSticky(rCxt.mrDoc, nDelta); + } + else + { + if (rRefRange.IsEndRowSticky(rCxt.mrDoc)) + // Sticky end not affected. + return STICKY; + + // The reference range is truncated on the bottom. + SCROW nDelta = rDeletedRange.aStart.Row() - rRefRange.aEnd.Row() - 1; + rRefRange.IncEndRowSticky(rCxt.mrDoc, nDelta); + } + return SHRUNK; + } + + return UNMODIFIED; +} + +bool expandRange( const sc::RefUpdateContext& rCxt, ScRange& rRefRange, const ScRange& rSelectedRange, + const ScComplexRefData& rRef ) +{ + if (!rSelectedRange.Intersects(rRefRange)) + return false; + + if (rCxt.mnColDelta > 0) + { + if (rRef.IsEntireRow(rCxt.mrDoc.GetSheetLimits())) + // Entire rows are not affected, columns are anchored. + return false; + + // Insert and shifting right. + if (rRefRange.aStart.Row() < rSelectedRange.aStart.Row() || rSelectedRange.aEnd.Row() < rRefRange.aEnd.Row()) + // Selected range is only partially overlapping in vertical direction. Bail out. + return false; + + if (rCxt.mrDoc.IsExpandRefs()) + { + if (rRefRange.aEnd.Col() - rRefRange.aStart.Col() < 1) + // Reference must be at least two columns wide. + return false; + } + else + { + if (rSelectedRange.aStart.Col() <= rRefRange.aStart.Col()) + // Selected range is at the left end and the edge expansion is turned off. No expansion. + return false; + } + + if (rRefRange.IsEndColSticky(rCxt.mrDoc)) + // Sticky end not affected. + return false; + + // Move the last column position to the right. + SCCOL nDelta = rSelectedRange.aEnd.Col() - rSelectedRange.aStart.Col() + 1; + rRefRange.IncEndColSticky(rCxt.mrDoc, nDelta); + return true; + } + else if (rCxt.mnRowDelta > 0) + { + if (rRef.IsEntireCol(rCxt.mrDoc.GetSheetLimits())) + // Entire columns are not affected, rows are anchored. + return false; + + // Insert and shifting down. + if (rRefRange.aStart.Col() < rSelectedRange.aStart.Col() || rSelectedRange.aEnd.Col() < rRefRange.aEnd.Col()) + // Selected range is only partially overlapping in horizontal direction. Bail out. + return false; + + if (rCxt.mrDoc.IsExpandRefs()) + { + if (rRefRange.aEnd.Row() - rRefRange.aStart.Row() < 1) + // Reference must be at least two rows tall. + return false; + } + else + { + if (rSelectedRange.aStart.Row() <= rRefRange.aStart.Row()) + // Selected range is at the top end and the edge expansion is turned off. No expansion. + return false; + } + + if (rRefRange.IsEndRowSticky(rCxt.mrDoc)) + // Sticky end not affected. + return false; + + // Move the last row position down. + SCROW nDelta = rSelectedRange.aEnd.Row() - rSelectedRange.aStart.Row() + 1; + rRefRange.IncEndRowSticky(rCxt.mrDoc, nDelta); + return true; + } + return false; +} + +/** + * Check if the referenced range is expandable when the selected range is + * not overlapping the referenced range. + */ +bool expandRangeByEdge( const sc::RefUpdateContext& rCxt, ScRange& rRefRange, const ScRange& rSelectedRange, + const ScComplexRefData& rRef ) +{ + if (!rCxt.mrDoc.IsExpandRefs()) + // Edge-expansion is turned off. + return false; + + if (rSelectedRange.aStart.Tab() > rRefRange.aStart.Tab() || rRefRange.aEnd.Tab() > rSelectedRange.aEnd.Tab()) + // Sheet references not within selected range. + return false; + + if (rCxt.mnColDelta > 0) + { + if (rRef.IsEntireRow(rCxt.mrDoc.GetSheetLimits())) + // Entire rows are not affected, columns are anchored. + return false; + + // Insert and shift right. + + if (rRefRange.aEnd.Col() - rRefRange.aStart.Col() < 1) + // Reference must be at least two columns wide. + return false; + + if (rRefRange.aStart.Row() < rSelectedRange.aStart.Row() || rSelectedRange.aEnd.Row() < rRefRange.aEnd.Row()) + // Selected range is only partially overlapping in vertical direction. Bail out. + return false; + + if (rSelectedRange.aStart.Col() - rRefRange.aEnd.Col() != 1) + // Selected range is not immediately adjacent. Bail out. + return false; + + if (rRefRange.IsEndColSticky(rCxt.mrDoc)) + // Sticky end not affected. + return false; + + // Move the last column position to the right. + SCCOL nDelta = rSelectedRange.aEnd.Col() - rSelectedRange.aStart.Col() + 1; + rRefRange.IncEndColSticky(rCxt.mrDoc, nDelta); + return true; + } + else if (rCxt.mnRowDelta > 0) + { + if (rRef.IsEntireCol(rCxt.mrDoc.GetSheetLimits())) + // Entire columns are not affected, rows are anchored. + return false; + + if (rRefRange.aEnd.Row() - rRefRange.aStart.Row() < 1) + // Reference must be at least two rows tall. + return false; + + if (rRefRange.aStart.Col() < rSelectedRange.aStart.Col() || rSelectedRange.aEnd.Col() < rRefRange.aEnd.Col()) + // Selected range is only partially overlapping in horizontal direction. Bail out. + return false; + + if (rSelectedRange.aStart.Row() - rRefRange.aEnd.Row() != 1) + // Selected range is not immediately adjacent. Bail out. + return false; + + if (rRefRange.IsEndRowSticky(rCxt.mrDoc)) + // Sticky end not affected. + return false; + + // Move the last row position down. + SCROW nDelta = rSelectedRange.aEnd.Row() - rSelectedRange.aStart.Row() + 1; + rRefRange.IncEndRowSticky(rCxt.mrDoc, nDelta); + return true; + } + + return false; +} + +bool isNameModified( const sc::UpdatedRangeNames& rUpdatedNames, SCTAB nOldTab, const formula::FormulaToken& rToken ) +{ + SCTAB nTab = -1; + if (rToken.GetSheet() >= 0) + nTab = nOldTab; + + // Check if this named expression has been modified. + return rUpdatedNames.isNameUpdated(nTab, rToken.GetIndex()); +} + +bool isDBDataModified( const ScDocument& rDoc, const formula::FormulaToken& rToken ) +{ + // Check if this DBData has been modified. + const ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex( rToken.GetIndex()); + if (!pDBData) + return true; + + return pDBData->IsModified(); +} + +} + +sc::RefUpdateResult ScTokenArray::AdjustReferenceOnShift( const sc::RefUpdateContext& rCxt, const ScAddress& rOldPos ) +{ + ScRange aSelectedRange = getSelectedRange(rCxt); + + sc::RefUpdateResult aRes; + ScAddress aNewPos = rOldPos; + bool bCellShifted = rCxt.maRange.Contains(rOldPos); + if (bCellShifted) + { + ScAddress aErrorPos( ScAddress::UNINITIALIZED ); + if (!aNewPos.Move(rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta, aErrorPos, rCxt.mrDoc)) + { + assert(!"can't move"); + } + } + + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN); + for (size_t j=0; j<2; ++j) + { + FormulaToken** pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken** pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch (p->GetType()) + { + case svSingleRef: + { + ScSingleRefData& rRef = *p->GetSingleRef(); + ScAddress aAbs = rRef.toAbs(*mxSheetLimits, rOldPos); + + if (rCxt.isDeleted() && aSelectedRange.Contains(aAbs)) + { + // This reference is in the deleted region. + setRefDeleted(rRef, rCxt); + aRes.mbValueChanged = true; + break; + } + + if (!rCxt.isDeleted() && rRef.IsDeleted()) + { + // Check if the token has reference to previously deleted region. + ScAddress aCheckPos = rRef.toAbs(*mxSheetLimits, aNewPos); + if (rCxt.maRange.Contains(aCheckPos)) + { + restoreDeletedRef(rRef, rCxt); + aRes.mbValueChanged = true; + break; + } + } + + if (rCxt.maRange.Contains(aAbs)) + { + ScAddress aErrorPos( ScAddress::UNINITIALIZED ); + if (!aAbs.Move(rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta, aErrorPos, rCxt.mrDoc)) + aAbs = aErrorPos; + aRes.mbReferenceModified = true; + } + + rRef.SetAddress(*mxSheetLimits, aAbs, aNewPos); + } + break; + case svDoubleRef: + { + ScComplexRefData& rRef = *p->GetDoubleRef(); + ScRange aAbs = rRef.toAbs(*mxSheetLimits, rOldPos); + + if (rCxt.isDeleted()) + { + if (aSelectedRange.Contains(aAbs)) + { + // This reference is in the deleted region. + setRefDeleted(rRef, rCxt); + aRes.mbValueChanged = true; + break; + } + else if (aSelectedRange.Intersects(aAbs)) + { + const ShrinkResult eSR = shrinkRange(rCxt, aAbs, aSelectedRange, rRef); + if (eSR == SHRUNK) + { + // The reference range has been shrunk. + rRef.SetRange(*mxSheetLimits, aAbs, aNewPos); + aRes.mbValueChanged = true; + aRes.mbReferenceModified = true; + break; + } + else if (eSR == STICKY) + { + // The reference range stays the same but a + // new (empty) cell range is shifted in and + // may change the calculation result. + aRes.mbValueChanged = true; + // Sticky when intersecting the selected + // range means also that the other + // conditions below are not met, + // specifically not the + // if (rCxt.maRange.Contains(aAbs)) + // that is able to update the reference, + // but aSelectedRange does not intersect + // with rCxt.maRange so that can't happen + // and we can bail out early without + // updating the reference. + break; + } + } + } + + if (!rCxt.isDeleted() && rRef.IsDeleted()) + { + // Check if the token has reference to previously deleted region. + ScRange aCheckRange = rRef.toAbs(*mxSheetLimits, aNewPos); + if (aSelectedRange.Contains(aCheckRange)) + { + // This reference was previously in the deleted region. Restore it. + restoreDeletedRef(rRef, rCxt); + aRes.mbValueChanged = true; + break; + } + } + + if (rCxt.isInserted()) + { + if (expandRange(rCxt, aAbs, aSelectedRange, rRef)) + { + // The reference range has been expanded. + rRef.SetRange(*mxSheetLimits, aAbs, aNewPos); + aRes.mbValueChanged = true; + aRes.mbReferenceModified = true; + break; + } + + if (expandRangeByEdge(rCxt, aAbs, aSelectedRange, rRef)) + { + // The reference range has been expanded on the edge. + rRef.SetRange(*mxSheetLimits, aAbs, aNewPos); + aRes.mbValueChanged = true; + aRes.mbReferenceModified = true; + break; + } + } + + if (rCxt.maRange.Contains(aAbs)) + { + // We shift either by column or by row, not both, + // so moving the reference has only to be done in + // the non-sticky case. + if ((rCxt.mnRowDelta && rRef.IsEntireCol(rCxt.mrDoc.GetSheetLimits())) + || (rCxt.mnColDelta && rRef.IsEntireRow(rCxt.mrDoc.GetSheetLimits()))) + { + // In entire col/row, values are shifted within + // the reference, which affects all positional + // results like in MATCH or matrix positions. + aRes.mbValueChanged = true; + } + else + { + ScRange aErrorRange( ScAddress::UNINITIALIZED ); + if (!aAbs.MoveSticky(rCxt.mrDoc, rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta, aErrorRange)) + aAbs = aErrorRange; + aRes.mbReferenceModified = true; + } + } + else if (rCxt.maRange.Intersects(aAbs)) + { + // Part of the referenced range is being shifted. This + // will change the values of the range. + aRes.mbValueChanged = true; + } + + rRef.SetRange(*mxSheetLimits, aAbs, aNewPos); + } + break; + case svExternalSingleRef: + { + // For external reference, just reset the reference with + // respect to the new cell position. + ScSingleRefData& rRef = *p->GetSingleRef(); + ScAddress aAbs = rRef.toAbs(*mxSheetLimits, rOldPos); + rRef.SetAddress(*mxSheetLimits, aAbs, aNewPos); + } + break; + case svExternalDoubleRef: + { + // Same as above. + ScComplexRefData& rRef = *p->GetDoubleRef(); + ScRange aAbs = rRef.toAbs(*mxSheetLimits, rOldPos); + rRef.SetRange(*mxSheetLimits, aAbs, aNewPos); + } + break; + default: + ; + } + + // For ocTableRef p is the inner token of *pp, so have a separate + // condition here. + if ((*pp)->GetType() == svIndex) + { + switch ((*pp)->GetOpCode()) + { + case ocName: + { + SCTAB nOldTab = (*pp)->GetSheet(); + if (isNameModified(rCxt.maUpdatedNames, nOldTab, **pp)) + aRes.mbNameModified = true; + if (rCxt.mnTabDelta && + rCxt.maRange.aStart.Tab() <= nOldTab && nOldTab <= rCxt.maRange.aEnd.Tab()) + { + aRes.mbNameModified = true; + (*pp)->SetSheet( nOldTab + rCxt.mnTabDelta); + } + } + break; + case ocDBArea: + case ocTableRef: + if (isDBDataModified(rCxt.mrDoc, **pp)) + aRes.mbNameModified = true; + break; + default: + ; // nothing + } + } + } + } + + return aRes; +} + +sc::RefUpdateResult ScTokenArray::AdjustReferenceOnMove( + const sc::RefUpdateContext& rCxt, const ScAddress& rOldPos, const ScAddress& rNewPos ) +{ + sc::RefUpdateResult aRes; + + if (!rCxt.mnColDelta && !rCxt.mnRowDelta && !rCxt.mnTabDelta) + // The cell hasn't moved at all. + return aRes; + + // When moving, the range in the context is the destination range. We need + // to use the old range prior to the move for hit analysis. + ScRange aOldRange = rCxt.maRange; + ScRange aErrorMoveRange( ScAddress::UNINITIALIZED ); + if (!aOldRange.Move(-rCxt.mnColDelta, -rCxt.mnRowDelta, -rCxt.mnTabDelta, aErrorMoveRange, rCxt.mrDoc)) + { + assert(!"can't move"); + } + + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN); + for (size_t j=0; j<2; ++j) + { + FormulaToken** pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken** pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch (p->GetType()) + { + case svSingleRef: + { + ScSingleRefData& rRef = *p->GetSingleRef(); + ScAddress aAbs = rRef.toAbs(*mxSheetLimits, rOldPos); + + // Do not update the reference in transposed case (cut paste transposed). + // The reference will be updated in UpdateTranspose(). + // Additionally, do not update the references from cells within the moved + // range as they lead to #REF! errors here. These #REF! cannot by fixed + // later in UpdateTranspose(). + if (rCxt.mbTransposed && (aOldRange.Contains(rOldPos) || aOldRange.Contains(aAbs))) + break; + + if (aOldRange.Contains(aAbs)) + { + ScAddress aErrorPos( ScAddress::UNINITIALIZED ); + if (!aAbs.Move(rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta, aErrorPos, rCxt.mrDoc)) + aAbs = aErrorPos; + aRes.mbReferenceModified = true; + } + else if (rCxt.maRange.Contains(aAbs)) + { + // Referenced cell has been overwritten. + aRes.mbValueChanged = true; + } + + rRef.SetAddress(*mxSheetLimits, aAbs, rNewPos); + rRef.SetFlag3D(rRef.IsFlag3D() || !rRef.IsTabRel() || aAbs.Tab() != rNewPos.Tab()); + } + break; + case svDoubleRef: + { + ScComplexRefData& rRef = *p->GetDoubleRef(); + ScRange aAbs = rRef.toAbs(*mxSheetLimits, rOldPos); + + // Do not update the reference in transposed case (cut paste transposed). + // The reference will be updated in UpdateTranspose(). + // Additionally, do not update the references from cells within the moved + // range as they lead to #REF! errors here. These #REF! cannot by fixed + // later in UpdateTranspose(). + if (rCxt.mbTransposed && (aOldRange.Contains(rOldPos) || aOldRange.Contains(aAbs))) + break; + + if (aOldRange.Contains(aAbs)) + { + ScRange aErrorRange( ScAddress::UNINITIALIZED ); + if (!aAbs.Move(rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta, aErrorRange, rCxt.mrDoc)) + aAbs = aErrorRange; + aRes.mbReferenceModified = true; + } + else if (rCxt.maRange.Contains(aAbs)) + { + // Referenced range has been entirely overwritten. + aRes.mbValueChanged = true; + } + + rRef.SetRange(*mxSheetLimits, aAbs, rNewPos); + bool b1, b2; + if (aAbs.aStart.Tab() != aAbs.aEnd.Tab()) + { + // More than one sheet referenced => has to have + // both 3D flags. + b1 = b2 = true; + } + else + { + // Keep given 3D flag even for relative sheet + // reference to same sheet. + // Absolute sheet reference => set 3D flag. + // Reference to another sheet => set 3D flag. + b1 = rRef.Ref1.IsFlag3D() || !rRef.Ref1.IsTabRel() || rNewPos.Tab() != aAbs.aStart.Tab(); + b2 = rRef.Ref2.IsFlag3D() || !rRef.Ref2.IsTabRel() || rNewPos.Tab() != aAbs.aEnd.Tab(); + // End part has 3D flag => start part must have it too. + if (b2) + b1 = true; + // End part sheet reference is identical to start + // part sheet reference and end part sheet + // reference was not explicitly given => clear end + // part 3D flag. + if (b1 && b2 && rRef.Ref1.IsTabRel() == rRef.Ref2.IsTabRel() && !rRef.Ref2.IsFlag3D()) + b2 = false; + } + rRef.Ref1.SetFlag3D(b1); + rRef.Ref2.SetFlag3D(b2); + } + break; + case svExternalSingleRef: + { + ScSingleRefData& rRef = *p->GetSingleRef(); + ScAddress aAbs = rRef.toAbs(*mxSheetLimits, rOldPos); + rRef.SetAddress(*mxSheetLimits, aAbs, rNewPos); + } + break; + case svExternalDoubleRef: + { + ScComplexRefData& rRef = *p->GetDoubleRef(); + ScRange aAbs = rRef.toAbs(*mxSheetLimits, rOldPos); + rRef.SetRange(*mxSheetLimits, aAbs, rNewPos); + } + break; + default: + ; + } + + // For ocTableRef p is the inner token of *pp, so have a separate + // condition here. + if ((*pp)->GetType() == svIndex) + { + switch ((*pp)->GetOpCode()) + { + case ocName: + { + SCTAB nOldTab = (*pp)->GetSheet(); + if (isNameModified(rCxt.maUpdatedNames, nOldTab, **pp)) + aRes.mbNameModified = true; + } + break; + case ocDBArea: + case ocTableRef: + if (isDBDataModified(rCxt.mrDoc, **pp)) + aRes.mbNameModified = true; + break; + default: + ; // nothing + } + } + } + } + + return aRes; +} + +void ScTokenArray::MoveReferenceColReorder( + const ScAddress& rPos, SCTAB nTab, SCROW nRow1, SCROW nRow2, const sc::ColRowReorderMapType& rColMap ) +{ + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN); + for (size_t j=0; j<2; ++j) + { + FormulaToken** pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken** pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch (p->GetType()) + { + case svSingleRef: + { + ScSingleRefData& rRef = *p->GetSingleRef(); + ScAddress aAbs = rRef.toAbs(*mxSheetLimits, rPos); + + if (aAbs.Tab() == nTab && nRow1 <= aAbs.Row() && aAbs.Row() <= nRow2) + { + // Inside reordered row range. + sc::ColRowReorderMapType::const_iterator it = rColMap.find(aAbs.Col()); + if (it != rColMap.end()) + { + // This column is reordered. + SCCOL nNewCol = it->second; + aAbs.SetCol(nNewCol); + rRef.SetAddress(*mxSheetLimits, aAbs, rPos); + } + } + } + break; + case svDoubleRef: + { + ScComplexRefData& rRef = *p->GetDoubleRef(); + ScRange aAbs = rRef.toAbs(*mxSheetLimits, rPos); + + if (aAbs.aStart.Tab() != aAbs.aEnd.Tab()) + // Must be a single-sheet reference. + break; + + if (aAbs.aStart.Col() != aAbs.aEnd.Col()) + // Whole range must fit in a single column. + break; + + if (aAbs.aStart.Tab() == nTab && nRow1 <= aAbs.aStart.Row() && aAbs.aEnd.Row() <= nRow2) + { + // Inside reordered row range. + sc::ColRowReorderMapType::const_iterator it = rColMap.find(aAbs.aStart.Col()); + if (it != rColMap.end()) + { + // This column is reordered. + SCCOL nNewCol = it->second; + aAbs.aStart.SetCol(nNewCol); + aAbs.aEnd.SetCol(nNewCol); + rRef.SetRange(*mxSheetLimits, aAbs, rPos); + } + } + } + break; + default: + ; + } + } + } +} + +void ScTokenArray::MoveReferenceRowReorder( const ScAddress& rPos, SCTAB nTab, SCCOL nCol1, SCCOL nCol2, const sc::ColRowReorderMapType& rRowMap ) +{ + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN); + for (size_t j=0; j<2; ++j) + { + FormulaToken** pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken** pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch (p->GetType()) + { + case svSingleRef: + { + ScSingleRefData& rRef = *p->GetSingleRef(); + ScAddress aAbs = rRef.toAbs(*mxSheetLimits, rPos); + + if (aAbs.Tab() == nTab && nCol1 <= aAbs.Col() && aAbs.Col() <= nCol2) + { + // Inside reordered column range. + sc::ColRowReorderMapType::const_iterator it = rRowMap.find(aAbs.Row()); + if (it != rRowMap.end()) + { + // This column is reordered. + SCROW nNewRow = it->second; + aAbs.SetRow(nNewRow); + rRef.SetAddress(*mxSheetLimits, aAbs, rPos); + } + } + } + break; + case svDoubleRef: + { + ScComplexRefData& rRef = *p->GetDoubleRef(); + ScRange aAbs = rRef.toAbs(*mxSheetLimits, rPos); + + if (aAbs.aStart.Tab() != aAbs.aEnd.Tab()) + // Must be a single-sheet reference. + break; + + if (aAbs.aStart.Row() != aAbs.aEnd.Row()) + // Whole range must fit in a single row. + break; + + if (aAbs.aStart.Tab() == nTab && nCol1 <= aAbs.aStart.Col() && aAbs.aEnd.Col() <= nCol2) + { + // Inside reordered column range. + sc::ColRowReorderMapType::const_iterator it = rRowMap.find(aAbs.aStart.Row()); + if (it != rRowMap.end()) + { + // This row is reordered. + SCROW nNewRow = it->second; + aAbs.aStart.SetRow(nNewRow); + aAbs.aEnd.SetRow(nNewRow); + rRef.SetRange(*mxSheetLimits, aAbs, rPos); + } + } + } + break; + default: + ; + } + } + } +} + +namespace { + +bool adjustSingleRefInName( + ScSingleRefData& rRef, const sc::RefUpdateContext& rCxt, const ScAddress& rPos, + ScComplexRefData* pEndOfComplex ) +{ + ScAddress aAbs = rRef.toAbs(rCxt.mrDoc, rPos); + + if (aAbs.Tab() < rCxt.maRange.aStart.Tab() || rCxt.maRange.aEnd.Tab() < aAbs.Tab()) + { + // This references a sheet that has not shifted. Don't change it. + return false; + } + + if (!rCxt.maRange.Contains(rRef.toAbs(rCxt.mrDoc, rPos))) + return false; + + bool bChanged = false; + + if (rCxt.mnColDelta && !rRef.IsColRel()) + { + // Adjust absolute column reference. + if (rCxt.maRange.aStart.Col() <= rRef.Col() && rRef.Col() <= rCxt.maRange.aEnd.Col()) + { + if (pEndOfComplex) + { + if (pEndOfComplex->IncEndColSticky(rCxt.mrDoc, rCxt.mnColDelta, rPos)) + bChanged = true; + } + else + { + rRef.IncCol(rCxt.mnColDelta); + bChanged = true; + } + } + } + + if (rCxt.mnRowDelta && !rRef.IsRowRel()) + { + // Adjust absolute row reference. + if (rCxt.maRange.aStart.Row() <= rRef.Row() && rRef.Row() <= rCxt.maRange.aEnd.Row()) + { + if (pEndOfComplex) + { + if (pEndOfComplex->IncEndRowSticky(rCxt.mrDoc, rCxt.mnRowDelta, rPos)) + bChanged = true; + } + else + { + rRef.IncRow(rCxt.mnRowDelta); + bChanged = true; + } + } + } + + if (!rRef.IsTabRel() && rCxt.mnTabDelta) + { + // Sheet range has already been checked above. + rRef.IncTab(rCxt.mnTabDelta); + bChanged = true; + } + + return bChanged; +} + +bool adjustDoubleRefInName( + ScComplexRefData& rRef, const sc::RefUpdateContext& rCxt, const ScAddress& rPos ) +{ + bool bRefChanged = false; + if (rCxt.mrDoc.IsExpandRefs()) + { + if (rCxt.mnRowDelta > 0 && !rRef.Ref1.IsRowRel() && !rRef.Ref2.IsRowRel()) + { + ScRange aAbs = rRef.toAbs(rCxt.mrDoc, rPos); + // Expand only if at least two rows tall. + if (aAbs.aStart.Row() < aAbs.aEnd.Row()) + { + // Check and see if we should expand the range at the top. + ScRange aSelectedRange = getSelectedRange(rCxt); + if (aSelectedRange.Intersects(aAbs)) + { + // Selection intersects the referenced range. Only expand the + // bottom position. + rRef.IncEndRowSticky(rCxt.mrDoc, rCxt.mnRowDelta, rPos); + return true; + } + } + } + if (rCxt.mnColDelta > 0 && !rRef.Ref1.IsColRel() && !rRef.Ref2.IsColRel()) + { + ScRange aAbs = rRef.toAbs(rCxt.mrDoc, rPos); + // Expand only if at least two columns wide. + if (aAbs.aStart.Col() < aAbs.aEnd.Col()) + { + // Check and see if we should expand the range at the left. + ScRange aSelectedRange = getSelectedRange(rCxt); + if (aSelectedRange.Intersects(aAbs)) + { + // Selection intersects the referenced range. Only expand the + // right position. + rRef.IncEndColSticky(rCxt.mrDoc, rCxt.mnColDelta, rPos); + return true; + } + } + } + } + + if ((rCxt.mnRowDelta && rRef.IsEntireCol(rCxt.mrDoc.GetSheetLimits())) + || (rCxt.mnColDelta && rRef.IsEntireRow(rCxt.mrDoc.GetSheetLimits()))) + { + sc::RefUpdateContext aCxt( rCxt.mrDoc); + // We only need a few parameters of RefUpdateContext. + aCxt.maRange = rCxt.maRange; + aCxt.mnColDelta = rCxt.mnColDelta; + aCxt.mnRowDelta = rCxt.mnRowDelta; + aCxt.mnTabDelta = rCxt.mnTabDelta; + + // References to entire col/row are not to be adjusted in the other axis. + if (aCxt.mnRowDelta && rRef.IsEntireCol(rCxt.mrDoc.GetSheetLimits())) + aCxt.mnRowDelta = 0; + if (aCxt.mnColDelta && rRef.IsEntireRow(rCxt.mrDoc.GetSheetLimits())) + aCxt.mnColDelta = 0; + if (!aCxt.mnColDelta && !aCxt.mnRowDelta && !aCxt.mnTabDelta) + // early bailout + return bRefChanged; + + // Ref2 before Ref1 for sticky ends. + if (adjustSingleRefInName(rRef.Ref2, aCxt, rPos, &rRef)) + bRefChanged = true; + + if (adjustSingleRefInName(rRef.Ref1, aCxt, rPos, nullptr)) + bRefChanged = true; + } + else + { + // Ref2 before Ref1 for sticky ends. + if (adjustSingleRefInName(rRef.Ref2, rCxt, rPos, &rRef)) + bRefChanged = true; + + if (adjustSingleRefInName(rRef.Ref1, rCxt, rPos, nullptr)) + bRefChanged = true; + } + + return bRefChanged; +} + +} + +sc::RefUpdateResult ScTokenArray::AdjustReferenceInName( + const sc::RefUpdateContext& rCxt, const ScAddress& rPos ) +{ + if (rCxt.meMode == URM_MOVE) + return AdjustReferenceInMovedName(rCxt, rPos); + + sc::RefUpdateResult aRes; + + if (rCxt.meMode == URM_COPY) + // Copying cells does not modify named expressions. + return aRes; + + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN); + for (size_t j=0; j<2; ++j) + { + FormulaToken** pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken** pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch (p->GetType()) + { + case svSingleRef: + { + ScSingleRefData& rRef = *p->GetSingleRef(); + if (rCxt.mnRowDelta < 0) + { + // row(s) deleted. + + if (rRef.IsRowRel()) + // Don't modify relative references in names. + break; + + ScAddress aAbs = rRef.toAbs(rCxt.mrDoc, rPos); + + if (aAbs.Col() < rCxt.maRange.aStart.Col() || rCxt.maRange.aEnd.Col() < aAbs.Col()) + // column of the reference is not in the deleted column range. + break; + + if (aAbs.Tab() > rCxt.maRange.aEnd.Tab() || aAbs.Tab() < rCxt.maRange.aStart.Tab()) + // wrong tables + break; + + const SCROW nDelStartRow = rCxt.maRange.aStart.Row() + rCxt.mnRowDelta; + const SCROW nDelEndRow = nDelStartRow - rCxt.mnRowDelta - 1; + + if (nDelStartRow <= aAbs.Row() && aAbs.Row() <= nDelEndRow) + { + // This reference is deleted. + rRef.SetRowDeleted(true); + aRes.mbReferenceModified = true; + break; + } + } + else if (rCxt.mnColDelta < 0) + { + // column(s) deleted. + + if (rRef.IsColRel()) + // Don't modify relative references in names. + break; + + ScAddress aAbs = rRef.toAbs(rCxt.mrDoc, rPos); + + if (aAbs.Row() < rCxt.maRange.aStart.Row() || rCxt.maRange.aEnd.Row() < aAbs.Row()) + // row of the reference is not in the deleted row range. + break; + + if (aAbs.Tab() > rCxt.maRange.aEnd.Tab() || aAbs.Tab() < rCxt.maRange.aStart.Tab()) + // wrong tables + break; + + const SCCOL nDelStartCol = rCxt.maRange.aStart.Col() + rCxt.mnColDelta; + const SCCOL nDelEndCol = nDelStartCol - rCxt.mnColDelta - 1; + + if (nDelStartCol <= aAbs.Col() && aAbs.Col() <= nDelEndCol) + { + // This reference is deleted. + rRef.SetColDeleted(true); + aRes.mbReferenceModified = true; + break; + } + } + + if (adjustSingleRefInName(rRef, rCxt, rPos, nullptr)) + aRes.mbReferenceModified = true; + } + break; + case svDoubleRef: + { + ScComplexRefData& rRef = *p->GetDoubleRef(); + ScRange aAbs = rRef.toAbs(rCxt.mrDoc, rPos); + + if (aAbs.aStart.Tab() > rCxt.maRange.aEnd.Tab() || aAbs.aEnd.Tab() < rCxt.maRange.aStart.Tab()) + // Sheet references not affected. + break; + + if (rCxt.maRange.Contains(aAbs)) + { + // This range is entirely within the shifted region. + if (adjustDoubleRefInName(rRef, rCxt, rPos)) + aRes.mbReferenceModified = true; + } + else if (rCxt.mnRowDelta < 0) + { + // row(s) deleted. + + if (rRef.IsEntireCol(rCxt.mrDoc.GetSheetLimits())) + // Rows of entire columns are not affected. + break; + + if (rRef.Ref1.IsRowRel() || rRef.Ref2.IsRowRel()) + // Don't modify relative references in names. + break; + + if (aAbs.aStart.Col() < rCxt.maRange.aStart.Col() || rCxt.maRange.aEnd.Col() < aAbs.aEnd.Col()) + // column range of the reference is not entirely in the deleted column range. + break; + + ScRange aDeleted = rCxt.maRange; + aDeleted.aStart.IncRow(rCxt.mnRowDelta); + aDeleted.aEnd.SetRow(aDeleted.aStart.Row()-rCxt.mnRowDelta-1); + + if (aAbs.aEnd.Row() < aDeleted.aStart.Row() || aDeleted.aEnd.Row() < aAbs.aStart.Row()) + // reference range doesn't intersect with the deleted range. + break; + + if (aDeleted.aStart.Row() <= aAbs.aStart.Row() && aAbs.aEnd.Row() <= aDeleted.aEnd.Row()) + { + // This reference is entirely deleted. + rRef.Ref1.SetRowDeleted(true); + rRef.Ref2.SetRowDeleted(true); + aRes.mbReferenceModified = true; + break; + } + + if (aAbs.aStart.Row() < aDeleted.aStart.Row()) + { + if (!aAbs.IsEndRowSticky(rCxt.mrDoc)) + { + if (aDeleted.aEnd.Row() < aAbs.aEnd.Row()) + // Deleted in the middle. Make the reference shorter. + rRef.Ref2.IncRow(rCxt.mnRowDelta); + else + // Deleted at tail end. Cut off the lower part. + rRef.Ref2.SetAbsRow(aDeleted.aStart.Row()-1); + } + } + else + { + // Deleted at the top. Cut the top off and shift up. + rRef.Ref1.SetAbsRow(aDeleted.aEnd.Row()+1); + rRef.Ref1.IncRow(rCxt.mnRowDelta); + if (!aAbs.IsEndRowSticky(rCxt.mrDoc)) + rRef.Ref2.IncRow(rCxt.mnRowDelta); + } + + aRes.mbReferenceModified = true; + } + else if (rCxt.mnColDelta < 0) + { + // column(s) deleted. + + if (rRef.IsEntireRow(rCxt.mrDoc.GetSheetLimits())) + // Rows of entire rows are not affected. + break; + + if (rRef.Ref1.IsColRel() || rRef.Ref2.IsColRel()) + // Don't modify relative references in names. + break; + + if (aAbs.aStart.Row() < rCxt.maRange.aStart.Row() || rCxt.maRange.aEnd.Row() < aAbs.aEnd.Row()) + // row range of the reference is not entirely in the deleted row range. + break; + + ScRange aDeleted = rCxt.maRange; + aDeleted.aStart.IncCol(rCxt.mnColDelta); + aDeleted.aEnd.SetCol(aDeleted.aStart.Col()-rCxt.mnColDelta-1); + + if (aAbs.aEnd.Col() < aDeleted.aStart.Col() || aDeleted.aEnd.Col() < aAbs.aStart.Col()) + // reference range doesn't intersect with the deleted range. + break; + + if (aDeleted.aStart.Col() <= aAbs.aStart.Col() && aAbs.aEnd.Col() <= aDeleted.aEnd.Col()) + { + // This reference is entirely deleted. + rRef.Ref1.SetColDeleted(true); + rRef.Ref2.SetColDeleted(true); + aRes.mbReferenceModified = true; + break; + } + + if (aAbs.aStart.Col() < aDeleted.aStart.Col()) + { + if (!aAbs.IsEndColSticky(rCxt.mrDoc)) + { + if (aDeleted.aEnd.Col() < aAbs.aEnd.Col()) + // Deleted in the middle. Make the reference shorter. + rRef.Ref2.IncCol(rCxt.mnColDelta); + else + // Deleted at tail end. Cut off the right part. + rRef.Ref2.SetAbsCol(aDeleted.aStart.Col()-1); + } + } + else + { + // Deleted at the left. Cut the left off and shift left. + rRef.Ref1.SetAbsCol(aDeleted.aEnd.Col()+1); + rRef.Ref1.IncCol(rCxt.mnColDelta); + if (!aAbs.IsEndColSticky(rCxt.mrDoc)) + rRef.Ref2.IncCol(rCxt.mnColDelta); + } + + aRes.mbReferenceModified = true; + } + else if (rCxt.maRange.Intersects(aAbs)) + { + if (rCxt.mnColDelta && rCxt.maRange.aStart.Row() <= aAbs.aStart.Row() && aAbs.aEnd.Row() <= rCxt.maRange.aEnd.Row()) + { + if (adjustDoubleRefInName(rRef, rCxt, rPos)) + aRes.mbReferenceModified = true; + } + if (rCxt.mnRowDelta && rCxt.maRange.aStart.Col() <= aAbs.aStart.Col() && aAbs.aEnd.Col() <= rCxt.maRange.aEnd.Col()) + { + if (adjustDoubleRefInName(rRef, rCxt, rPos)) + aRes.mbReferenceModified = true; + } + } + else if (rCxt.mnRowDelta > 0 && rCxt.mrDoc.IsExpandRefs()) + { + // Check if we could expand range reference by the bottom + // edge. For named expressions, we only expand absolute + // references. Reference must be at least two rows + // tall. + if (!rRef.Ref1.IsRowRel() && !rRef.Ref2.IsRowRel() && + aAbs.aStart.Row() < aAbs.aEnd.Row() && + aAbs.aEnd.Row()+1 == rCxt.maRange.aStart.Row()) + { + // Expand by the bottom edge. + rRef.Ref2.IncRow(rCxt.mnRowDelta); + aRes.mbReferenceModified = true; + } + } + else if (rCxt.mnColDelta > 0 && rCxt.mrDoc.IsExpandRefs()) + { + // Check if we could expand range reference by the right + // edge. For named expressions, we only expand absolute + // references. Reference must be at least two + // columns wide. + if (!rRef.Ref1.IsColRel() && !rRef.Ref2.IsColRel() && + aAbs.aStart.Col() < aAbs.aEnd.Col() && + aAbs.aEnd.Col()+1 == rCxt.maRange.aStart.Col()) + { + // Expand by the right edge. + rRef.Ref2.IncCol(rCxt.mnColDelta); + aRes.mbReferenceModified = true; + } + } + } + break; + default: + ; + } + } + } + + return aRes; +} + +sc::RefUpdateResult ScTokenArray::AdjustReferenceInMovedName( const sc::RefUpdateContext& rCxt, const ScAddress& rPos ) +{ + // When moving, the range is the destination range. + ScRange aOldRange = rCxt.maRange; + ScRange aErrorMoveRange( ScAddress::UNINITIALIZED ); + if (!aOldRange.Move(-rCxt.mnColDelta, -rCxt.mnRowDelta, -rCxt.mnTabDelta, aErrorMoveRange, rCxt.mrDoc)) + { + assert(!"can't move"); + } + + // In a named expression, we'll move the reference only when the reference + // is entirely absolute. + + sc::RefUpdateResult aRes; + + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN); + for (size_t j=0; j<2; ++j) + { + FormulaToken** pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken** pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch (p->GetType()) + { + case svSingleRef: + { + ScSingleRefData& rRef = *p->GetSingleRef(); + if (rRef.IsColRel() || rRef.IsRowRel() || rRef.IsTabRel()) + continue; + + ScAddress aAbs = rRef.toAbs(rCxt.mrDoc, rPos); + + // Do not update the reference in transposed case (cut paste transposed). + // The reference will be updated in UpdateTranspose(). + if (rCxt.mbTransposed && aOldRange.Contains(aAbs)) + break; + + if (aOldRange.Contains(aAbs)) + { + ScAddress aErrorPos( ScAddress::UNINITIALIZED ); + if (!aAbs.Move(rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta, aErrorPos, rCxt.mrDoc)) + aAbs = aErrorPos; + aRes.mbReferenceModified = true; + } + + rRef.SetAddress(rCxt.mrDoc.GetSheetLimits(), aAbs, rPos); + } + break; + case svDoubleRef: + { + ScComplexRefData& rRef = *p->GetDoubleRef(); + if (rRef.Ref1.IsColRel() || rRef.Ref1.IsRowRel() || rRef.Ref1.IsTabRel() || + rRef.Ref2.IsColRel() || rRef.Ref2.IsRowRel() || rRef.Ref2.IsTabRel()) + continue; + + ScRange aAbs = rRef.toAbs(rCxt.mrDoc, rPos); + + // Do not update the reference in transposed case (cut paste transposed). + // The reference will be updated in UpdateTranspose(). + if (rCxt.mbTransposed && aOldRange.Contains(aAbs)) + break; + + if (aOldRange.Contains(aAbs)) + { + ScRange aErrorRange( ScAddress::UNINITIALIZED ); + if (!aAbs.Move(rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta, aErrorRange, rCxt.mrDoc)) + aAbs = aErrorRange; + aRes.mbReferenceModified = true; + } + + rRef.SetRange(rCxt.mrDoc.GetSheetLimits(), aAbs, rPos); + } + break; + default: + ; + } + } + } + + return aRes; +} + +namespace { + +bool adjustSingleRefOnDeletedTab( const ScSheetLimits& rLimits, ScSingleRefData& rRef, SCTAB nDelPos, SCTAB nSheets, const ScAddress& rOldPos, const ScAddress& rNewPos ) +{ + ScAddress aAbs = rRef.toAbs(rLimits, rOldPos); + if (nDelPos <= aAbs.Tab() && aAbs.Tab() < nDelPos + nSheets) + { + rRef.SetTabDeleted(true); + return true; + } + + if (nDelPos < aAbs.Tab()) + { + // Reference sheet needs to be adjusted. + aAbs.IncTab(-1*nSheets); + rRef.SetAddress(rLimits, aAbs, rNewPos); + return true; + } + else if (rOldPos.Tab() != rNewPos.Tab()) + { + // Cell itself has moved. + rRef.SetAddress(rLimits, aAbs, rNewPos); + return true; + } + + return false; +} + +bool adjustSingleRefOnInsertedTab( const ScSheetLimits& rLimits, ScSingleRefData& rRef, SCTAB nInsPos, SCTAB nSheets, const ScAddress& rOldPos, const ScAddress& rNewPos ) +{ + ScAddress aAbs = rRef.toAbs(rLimits, rOldPos); + if (nInsPos <= aAbs.Tab()) + { + // Reference sheet needs to be adjusted. + aAbs.IncTab(nSheets); + rRef.SetAddress(rLimits, aAbs, rNewPos); + return true; + } + else if (rOldPos.Tab() != rNewPos.Tab()) + { + // Cell itself has moved. + rRef.SetAddress(rLimits, aAbs, rNewPos); + return true; + } + + return false; +} + +bool adjustDoubleRefOnDeleteTab(const ScSheetLimits& rLimits, ScComplexRefData& rRef, SCTAB nDelPos, SCTAB nSheets, const ScAddress& rOldPos, const ScAddress& rNewPos) +{ + ScSingleRefData& rRef1 = rRef.Ref1; + ScSingleRefData& rRef2 = rRef.Ref2; + ScAddress aStartPos = rRef1.toAbs(rLimits, rOldPos); + ScAddress aEndPos = rRef2.toAbs(rLimits, rOldPos); + bool bMoreThanOneTab = aStartPos.Tab() != aEndPos.Tab(); + bool bModified = false; + if (bMoreThanOneTab && aStartPos.Tab() == nDelPos && nDelPos + nSheets <= aEndPos.Tab()) + { + if (rRef1.IsTabRel() && aStartPos.Tab() < rOldPos.Tab()) + { + rRef1.IncTab(nSheets); + bModified = true; + } + } + else + { + bModified = adjustSingleRefOnDeletedTab(rLimits, rRef1, nDelPos, nSheets, rOldPos, rNewPos); + } + + if (bMoreThanOneTab && aEndPos.Tab() == nDelPos && aStartPos.Tab() <= nDelPos - nSheets) + { + if (!rRef2.IsTabRel() || rOldPos.Tab() < aEndPos.Tab()) + { + rRef2.IncTab(-nSheets); + bModified = true; + } + } + else + { + bModified |= adjustSingleRefOnDeletedTab(rLimits, rRef2, nDelPos, nSheets, rOldPos, rNewPos); + } + return bModified; +} + +} + +sc::RefUpdateResult ScTokenArray::AdjustReferenceOnDeletedTab( const sc::RefUpdateDeleteTabContext& rCxt, const ScAddress& rOldPos ) +{ + sc::RefUpdateResult aRes; + ScAddress aNewPos = rOldPos; + ScRangeUpdater::UpdateDeleteTab( aNewPos, rCxt); + + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN); + for (size_t j=0; j<2; ++j) + { + FormulaToken** pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken** pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch (p->GetType()) + { + case svSingleRef: + { + ScSingleRefData& rRef = *p->GetSingleRef(); + if (adjustSingleRefOnDeletedTab(*mxSheetLimits, rRef, rCxt.mnDeletePos, rCxt.mnSheets, rOldPos, aNewPos)) + aRes.mbReferenceModified = true; + } + break; + case svDoubleRef: + { + ScComplexRefData& rRef = *p->GetDoubleRef(); + aRes.mbReferenceModified |= adjustDoubleRefOnDeleteTab(*mxSheetLimits, rRef, rCxt.mnDeletePos, rCxt.mnSheets, rOldPos, aNewPos); + } + break; + default: + ; + } + + // For ocTableRef p is the inner token of *pp, so have a separate + // condition here. + if ((*pp)->GetType() == svIndex) + { + switch ((*pp)->GetOpCode()) + { + case ocName: + { + SCTAB nOldTab = (*pp)->GetSheet(); + if (isNameModified(rCxt.maUpdatedNames, nOldTab, **pp)) + aRes.mbNameModified = true; + if (rCxt.mnDeletePos <= nOldTab) + { + aRes.mbNameModified = true; + if (rCxt.mnDeletePos + rCxt.mnSheets <= nOldTab) + (*pp)->SetSheet( nOldTab - rCxt.mnSheets); + else + // Would point to a deleted sheet. Invalidate. + (*pp)->SetSheet( SCTAB_MAX); + } + } + break; + case ocDBArea: + case ocTableRef: + if (isDBDataModified(rCxt.mrDoc, **pp)) + aRes.mbNameModified = true; + break; + default: + ; // nothing + } + } + } + } + return aRes; +} + +sc::RefUpdateResult ScTokenArray::AdjustReferenceOnInsertedTab( const sc::RefUpdateInsertTabContext& rCxt, const ScAddress& rOldPos ) +{ + sc::RefUpdateResult aRes; + ScAddress aNewPos = rOldPos; + if (rCxt.mnInsertPos <= rOldPos.Tab()) + aNewPos.IncTab(rCxt.mnSheets); + + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN); + for (size_t j=0; j<2; ++j) + { + FormulaToken** pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken** pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch (p->GetType()) + { + case svSingleRef: + { + ScSingleRefData& rRef = *p->GetSingleRef(); + if (adjustSingleRefOnInsertedTab(*mxSheetLimits, rRef, rCxt.mnInsertPos, rCxt.mnSheets, rOldPos, aNewPos)) + aRes.mbReferenceModified = true; + } + break; + case svDoubleRef: + { + ScComplexRefData& rRef = *p->GetDoubleRef(); + if (adjustSingleRefOnInsertedTab(*mxSheetLimits, rRef.Ref1, rCxt.mnInsertPos, rCxt.mnSheets, rOldPos, aNewPos)) + aRes.mbReferenceModified = true; + if (adjustSingleRefOnInsertedTab(*mxSheetLimits, rRef.Ref2, rCxt.mnInsertPos, rCxt.mnSheets, rOldPos, aNewPos)) + aRes.mbReferenceModified = true; + } + break; + default: + ; + } + + // For ocTableRef p is the inner token of *pp, so have a separate + // condition here. + if ((*pp)->GetType() == svIndex) + { + switch ((*pp)->GetOpCode()) + { + case ocName: + { + SCTAB nOldTab = (*pp)->GetSheet(); + if (isNameModified(rCxt.maUpdatedNames, nOldTab, **pp)) + aRes.mbNameModified = true; + if (rCxt.mnInsertPos <= nOldTab) + { + aRes.mbNameModified = true; + (*pp)->SetSheet( nOldTab + rCxt.mnSheets); + } + } + break; + case ocDBArea: + case ocTableRef: + if (isDBDataModified(rCxt.mrDoc, **pp)) + aRes.mbNameModified = true; + break; + default: + ; // nothing + } + } + } + } + return aRes; +} + +namespace { + +bool adjustTabOnMove( ScAddress& rPos, const sc::RefUpdateMoveTabContext& rCxt ) +{ + SCTAB nNewTab = rCxt.getNewTab(rPos.Tab()); + if (nNewTab == rPos.Tab()) + return false; + + rPos.SetTab(nNewTab); + return true; +} + +} + +sc::RefUpdateResult ScTokenArray::AdjustReferenceOnMovedTab( const sc::RefUpdateMoveTabContext& rCxt, const ScAddress& rOldPos ) +{ + sc::RefUpdateResult aRes; + if (rCxt.mnOldPos == rCxt.mnNewPos) + return aRes; + + ScAddress aNewPos = rOldPos; + if (adjustTabOnMove(aNewPos, rCxt)) + { + aRes.mbReferenceModified = true; + aRes.mbValueChanged = true; + aRes.mnTab = aNewPos.Tab(); // this sets the new tab position used when deleting + } + + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN); + for (size_t j=0; j<2; ++j) + { + FormulaToken** pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken** pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch (p->GetType()) + { + case svSingleRef: + { + ScSingleRefData& rRef = *p->GetSingleRef(); + ScAddress aAbs = rRef.toAbs(*mxSheetLimits, rOldPos); + if (adjustTabOnMove(aAbs, rCxt)) + aRes.mbReferenceModified = true; + rRef.SetAddress(*mxSheetLimits, aAbs, aNewPos); + } + break; + case svDoubleRef: + { + ScComplexRefData& rRef = *p->GetDoubleRef(); + ScRange aAbs = rRef.toAbs(*mxSheetLimits, rOldPos); + if (adjustTabOnMove(aAbs.aStart, rCxt)) + aRes.mbReferenceModified = true; + if (adjustTabOnMove(aAbs.aEnd, rCxt)) + aRes.mbReferenceModified = true; + rRef.SetRange(*mxSheetLimits, aAbs, aNewPos); + } + break; + default: + ; + } + + // For ocTableRef p is the inner token of *pp, so have a separate + // condition here. + if ((*pp)->GetType() == svIndex) + { + switch ((*pp)->GetOpCode()) + { + case ocName: + { + SCTAB nOldTab = (*pp)->GetSheet(); + if (isNameModified(rCxt.maUpdatedNames, nOldTab, **pp)) + aRes.mbNameModified = true; + SCTAB nNewTab = rCxt.getNewTab( nOldTab); + if (nNewTab != nOldTab) + { + aRes.mbNameModified = true; + (*pp)->SetSheet( nNewTab); + } + } + break; + case ocDBArea: + case ocTableRef: + if (isDBDataModified(rCxt.mrDoc, **pp)) + aRes.mbNameModified = true; + break; + default: + ; // nothing + } + } + } + } + + return aRes; +} + +void ScTokenArray::AdjustReferenceOnMovedOrigin( const ScAddress& rOldPos, const ScAddress& rNewPos ) +{ + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN); + for (size_t j=0; j<2; ++j) + { + FormulaToken** pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken** pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch (p->GetType()) + { + case svSingleRef: + case svExternalSingleRef: + { + ScSingleRefData& rRef = *p->GetSingleRef(); + ScAddress aAbs = rRef.toAbs(*mxSheetLimits, rOldPos); + rRef.SetAddress(*mxSheetLimits, aAbs, rNewPos); + } + break; + case svDoubleRef: + case svExternalDoubleRef: + { + ScComplexRefData& rRef = *p->GetDoubleRef(); + ScRange aAbs = rRef.toAbs(*mxSheetLimits, rOldPos); + rRef.SetRange(*mxSheetLimits, aAbs, rNewPos); + } + break; + default: + ; + } + } + } +} + +void ScTokenArray::AdjustReferenceOnMovedOriginIfOtherSheet( const ScAddress& rOldPos, const ScAddress& rNewPos ) +{ + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN); + for (size_t j=0; j<2; ++j) + { + FormulaToken** pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken** pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + bool bAdjust = false; + switch (p->GetType()) + { + case svExternalSingleRef: + bAdjust = true; // always + [[fallthrough]]; + case svSingleRef: + { + ScSingleRefData& rRef = *p->GetSingleRef(); + ScAddress aAbs = rRef.toAbs(*mxSheetLimits, rOldPos); + if (!bAdjust) + bAdjust = (aAbs.Tab() != rOldPos.Tab()); + if (bAdjust) + rRef.SetAddress(*mxSheetLimits, aAbs, rNewPos); + } + break; + case svExternalDoubleRef: + bAdjust = true; // always + [[fallthrough]]; + case svDoubleRef: + { + ScComplexRefData& rRef = *p->GetDoubleRef(); + ScRange aAbs = rRef.toAbs(*mxSheetLimits, rOldPos); + if (!bAdjust) + bAdjust = (rOldPos.Tab() < aAbs.aStart.Tab() || aAbs.aEnd.Tab() < rOldPos.Tab()); + if (bAdjust) + rRef.SetRange(*mxSheetLimits, aAbs, rNewPos); + } + break; + default: + ; + } + } + } +} + +void ScTokenArray::AdjustReferenceOnCopy( const ScAddress& rNewPos ) +{ + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN, false); + for (size_t j=0; j<2; ++j) + { + FormulaToken** pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken** pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch (p->GetType()) + { + case svDoubleRef: + { + ScComplexRefData& rRef = *p->GetDoubleRef(); + rRef.PutInOrder( rNewPos); + } + break; + default: + ; + } + } + } +} + +namespace { + +void clearTabDeletedFlag( const ScSheetLimits& rLimits, ScSingleRefData& rRef, const ScAddress& rPos, SCTAB nStartTab, SCTAB nEndTab ) +{ + if (!rRef.IsTabDeleted()) + return; + + ScAddress aAbs = rRef.toAbs(rLimits, rPos); + if (nStartTab <= aAbs.Tab() && aAbs.Tab() <= nEndTab) + rRef.SetTabDeleted(false); +} + +} + +void ScTokenArray::ClearTabDeleted( const ScAddress& rPos, SCTAB nStartTab, SCTAB nEndTab ) +{ + if (nEndTab < nStartTab) + return; + + FormulaToken** p = pCode.get(); + FormulaToken** pEnd = p + static_cast<size_t>(nLen); + for (; p != pEnd; ++p) + { + switch ((*p)->GetType()) + { + case svSingleRef: + { + formula::FormulaToken* pToken = *p; + ScSingleRefData& rRef = *pToken->GetSingleRef(); + clearTabDeletedFlag(*mxSheetLimits, rRef, rPos, nStartTab, nEndTab); + } + break; + case svDoubleRef: + { + formula::FormulaToken* pToken = *p; + ScComplexRefData& rRef = *pToken->GetDoubleRef(); + clearTabDeletedFlag(*mxSheetLimits, rRef.Ref1, rPos, nStartTab, nEndTab); + clearTabDeletedFlag(*mxSheetLimits, rRef.Ref2, rPos, nStartTab, nEndTab); + } + break; + default: + ; + } + } +} + +namespace { + +void checkBounds( + const ScSheetLimits& rLimits, + const ScAddress& rPos, SCROW nGroupLen, const ScRange& rCheckRange, + const ScSingleRefData& rRef, std::vector<SCROW>& rBounds, const ScRange* pDeletedRange ) +{ + if (!rRef.IsRowRel()) + return; + + ScRange aAbs(rRef.toAbs(rLimits, rPos)); + aAbs.aEnd.IncRow(nGroupLen-1); + if (!rCheckRange.Intersects(aAbs) && (!pDeletedRange || !pDeletedRange->Intersects(aAbs))) + return; + + // Get the boundary row positions. + if (aAbs.aEnd.Row() < rCheckRange.aStart.Row() && (!pDeletedRange || aAbs.aEnd.Row() < pDeletedRange->aStart.Row())) + // No intersections. + return; + + // rCheckRange may be a virtual non-existent row being shifted in. + if (aAbs.aStart.Row() <= rCheckRange.aStart.Row() && rCheckRange.aStart.Row() < rLimits.GetMaxRowCount()) + { + // +-+ <---- top + // | | + // +--+-+--+ <---- boundary row position + // | | | | + // | | + // +-------+ + + // Add offset from the reference top to the cell position. + SCROW nOffset = rCheckRange.aStart.Row() - aAbs.aStart.Row(); + rBounds.push_back(rPos.Row()+nOffset); + } + // Same for deleted range. + if (pDeletedRange && aAbs.aStart.Row() <= pDeletedRange->aStart.Row()) + { + SCROW nOffset = pDeletedRange->aStart.Row() - aAbs.aStart.Row(); + SCROW nRow = rPos.Row() + nOffset; + // Unlike for rCheckRange, for pDeletedRange nRow can be anywhere>=0. + if (rLimits.ValidRow(nRow)) + rBounds.push_back(nRow); + } + + if (aAbs.aEnd.Row() >= rCheckRange.aEnd.Row()) + { + // only check for end range + + // +-------+ + // | | + // | | | | + // +--+-+--+ <---- boundary row position + // | | + // +-+ + + // Ditto. + SCROW nOffset = rCheckRange.aEnd.Row() + 1 - aAbs.aStart.Row(); + rBounds.push_back(rPos.Row()+nOffset); + } + // Same for deleted range. + if (pDeletedRange && aAbs.aEnd.Row() >= pDeletedRange->aEnd.Row()) + { + SCROW nOffset = pDeletedRange->aEnd.Row() + 1 - aAbs.aStart.Row(); + SCROW nRow = rPos.Row() + nOffset; + // Unlike for rCheckRange, for pDeletedRange nRow can be ~anywhere. + if (rLimits.ValidRow(nRow)) + rBounds.push_back(nRow); + } +} + +void checkBounds( + const sc::RefUpdateContext& rCxt, const ScAddress& rPos, SCROW nGroupLen, + const ScSingleRefData& rRef, std::vector<SCROW>& rBounds) +{ + if (!rRef.IsRowRel()) + return; + + ScRange aDeletedRange( ScAddress::UNINITIALIZED ); + const ScRange* pDeletedRange = nullptr; + + ScRange aCheckRange = rCxt.maRange; + if (rCxt.meMode == URM_MOVE) + { + // Check bounds against the old range prior to the move. + ScRange aErrorRange( ScAddress::UNINITIALIZED ); + if (!aCheckRange.Move(-rCxt.mnColDelta, -rCxt.mnRowDelta, -rCxt.mnTabDelta, aErrorRange, rCxt.mrDoc)) + { + assert(!"can't move"); + } + + // Check bounds also against the range moved into. + pDeletedRange = &rCxt.maRange; + } + else if (rCxt.meMode == URM_INSDEL && + ((rCxt.mnColDelta < 0 && rCxt.maRange.aStart.Col() > 0) || + (rCxt.mnRowDelta < 0 && rCxt.maRange.aStart.Row() > 0))) + { + // Check bounds also against deleted range where cells are shifted + // into and references need to be invalidated. + aDeletedRange = getSelectedRange( rCxt); + pDeletedRange = &aDeletedRange; + } + + checkBounds(rCxt.mrDoc.GetSheetLimits(), rPos, nGroupLen, aCheckRange, rRef, rBounds, pDeletedRange); +} + +} + +void ScTokenArray::CheckRelativeReferenceBounds( + const sc::RefUpdateContext& rCxt, const ScAddress& rPos, SCROW nGroupLen, std::vector<SCROW>& rBounds ) const +{ + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN); + for (size_t j=0; j<2; ++j) + { + FormulaToken** pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken** pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch (p->GetType()) + { + case svSingleRef: + { + checkBounds(rCxt, rPos, nGroupLen, *p->GetSingleRef(), rBounds); + } + break; + case svDoubleRef: + { + const ScComplexRefData& rRef = *p->GetDoubleRef(); + checkBounds(rCxt, rPos, nGroupLen, rRef.Ref1, rBounds); + checkBounds(rCxt, rPos, nGroupLen, rRef.Ref2, rBounds); + } + break; + default: + ; + } + } + } +} + +void ScTokenArray::CheckRelativeReferenceBounds( + const ScAddress& rPos, SCROW nGroupLen, const ScRange& rRange, std::vector<SCROW>& rBounds ) const +{ + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN); + for (size_t j=0; j<2; ++j) + { + FormulaToken** pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken** pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch (p->GetType()) + { + case svSingleRef: + { + const ScSingleRefData& rRef = *p->GetSingleRef(); + checkBounds(*mxSheetLimits, rPos, nGroupLen, rRange, rRef, rBounds, nullptr); + } + break; + case svDoubleRef: + { + const ScComplexRefData& rRef = *p->GetDoubleRef(); + checkBounds(*mxSheetLimits, rPos, nGroupLen, rRange, rRef.Ref1, rBounds, nullptr); + checkBounds(*mxSheetLimits, rPos, nGroupLen, rRange, rRef.Ref2, rBounds, nullptr); + } + break; + default: + ; + } + } + } +} + +void ScTokenArray::CheckExpandReferenceBounds( + const sc::RefUpdateContext& rCxt, const ScAddress& rPos, SCROW nGroupLen, std::vector<SCROW>& rBounds ) const +{ + const SCROW nInsRow = rCxt.maRange.aStart.Row(); + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN); + for (size_t j=0; j<2; ++j) + { + FormulaToken* const * pp = aPtrs.maPointerRange[j].mpStart; + const FormulaToken* const * pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + const FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch (p->GetType()) + { + case svDoubleRef: + { + const ScComplexRefData& rRef = *p->GetDoubleRef(); + bool bStartRowRelative = rRef.Ref1.IsRowRel(); + bool bEndRowRelative = rRef.Ref2.IsRowRel(); + + // For absolute references nothing needs to be done, they stay + // the same for all and if to be expanded the group will be + // adjusted later. + if (!bStartRowRelative && !bEndRowRelative) + break; // switch + + ScRange aAbsStart(rRef.toAbs(*mxSheetLimits, rPos)); + ScAddress aPos(rPos); + aPos.IncRow(nGroupLen); + ScRange aAbsEnd(rRef.toAbs(*mxSheetLimits, aPos)); + // References must be at least two rows to be expandable. + if ((aAbsStart.aEnd.Row() - aAbsStart.aStart.Row() < 1) && + (aAbsEnd.aEnd.Row() - aAbsEnd.aStart.Row() < 1)) + break; // switch + + // Only need to process if an edge may be touching the + // insertion row anywhere within the run of the group. + if (!((aAbsStart.aStart.Row() <= nInsRow && nInsRow <= aAbsEnd.aStart.Row()) || + (aAbsStart.aEnd.Row() <= nInsRow && nInsRow <= aAbsEnd.aEnd.Row()))) + break; // switch + + SCROW nStartRow = aAbsStart.aStart.Row(); + SCROW nEndRow = aAbsStart.aEnd.Row(); + // Position on first relevant range. + SCROW nOffset = 0; + if (nEndRow + 1 < nInsRow) + { + if (bEndRowRelative) + { + nOffset = nInsRow - nEndRow - 1; + nEndRow += nOffset; + if (bStartRowRelative) + nStartRow += nOffset; + } + else // bStartRowRelative==true + { + nOffset = nInsRow - nStartRow; + nStartRow += nOffset; + // Start is overtaking End, swap. + bStartRowRelative = false; + bEndRowRelative = true; + } + } + for (SCROW i = nOffset; i < nGroupLen; ++i) + { + bool bSplit = (nStartRow == nInsRow || nEndRow + 1 == nInsRow); + if (bSplit) + rBounds.push_back( rPos.Row() + i); + + if (bEndRowRelative) + ++nEndRow; + if (bStartRowRelative) + { + ++nStartRow; + if (!bEndRowRelative && nStartRow == nEndRow) + { + // Start is overtaking End, swap. + bStartRowRelative = false; + bEndRowRelative = true; + } + } + if (nInsRow < nStartRow || (!bStartRowRelative && nInsRow <= nEndRow)) + { + if (bSplit && (++i < nGroupLen)) + rBounds.push_back( rPos.Row() + i); + break; // for, out of range now + } + } + } + break; + default: + ; + } + } + } +} + +namespace { + +void appendDouble( const sc::TokenStringContext& rCxt, OUStringBuffer& rBuf, double fVal ) +{ + if (rCxt.mxOpCodeMap->isEnglish()) + { + rtl::math::doubleToUStringBuffer( + rBuf, fVal, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, '.', true); + } + else + { + SvtSysLocale aSysLocale; + rtl::math::doubleToUStringBuffer( + rBuf, fVal, + rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, + aSysLocale.GetLocaleData().getNumDecimalSep()[0], true); + } +} + +void appendString( OUStringBuffer& rBuf, const OUString& rStr ) +{ + rBuf.append('"'); + rBuf.append(rStr.replaceAll("\"", "\"\"")); + rBuf.append('"'); +} + +void appendTokenByType( ScSheetLimits& rLimits, sc::TokenStringContext& rCxt, OUStringBuffer& rBuf, const FormulaToken& rToken, + const ScAddress& rPos, bool bFromRangeName ) +{ + if (rToken.IsExternalRef()) + { + size_t nFileId = rToken.GetIndex(); + OUString aTabName = rToken.GetString().getString(); + if (nFileId >= rCxt.maExternalFileNames.size()) + // out of bound + return; + + OUString aFileName = rCxt.maExternalFileNames[nFileId]; + + switch (rToken.GetType()) + { + case svExternalName: + rBuf.append(rCxt.mpRefConv->makeExternalNameStr(nFileId, aFileName, aTabName)); + break; + case svExternalSingleRef: + rCxt.mpRefConv->makeExternalRefStr( + rLimits, rBuf, rPos, nFileId, aFileName, aTabName, *rToken.GetSingleRef()); + break; + case svExternalDoubleRef: + { + sc::TokenStringContext::IndexNamesMapType::const_iterator it = + rCxt.maExternalCachedTabNames.find(nFileId); + + if (it == rCxt.maExternalCachedTabNames.end()) + return; + + rCxt.mpRefConv->makeExternalRefStr( + rLimits, rBuf, rPos, nFileId, aFileName, it->second, aTabName, + *rToken.GetDoubleRef()); + } + break; + default: + // warning, not error, otherwise we may end up with a never + // ending message box loop if this was the cursor cell to be redrawn. + OSL_FAIL("appendTokenByType: unknown type of ocExternalRef"); + } + return; + } + + OpCode eOp = rToken.GetOpCode(); + switch (rToken.GetType()) + { + case svDouble: + appendDouble(rCxt, rBuf, rToken.GetDouble()); + break; + case svString: + { + OUString aStr = rToken.GetString().getString(); + if (eOp == ocBad || eOp == ocStringXML) + { + rBuf.append(aStr); + return; + } + + appendString(rBuf, aStr); + } + break; + case svSingleRef: + { + if (rCxt.mpRefConv) + { + const ScSingleRefData& rRef = *rToken.GetSingleRef(); + ScComplexRefData aRef; + aRef.Ref1 = rRef; + aRef.Ref2 = rRef; + rCxt.mpRefConv->makeRefStr(rLimits, rBuf, rCxt.meGram, rPos, rCxt.maErrRef, rCxt.maTabNames, aRef, true, + bFromRangeName); + } + else + rBuf.append(rCxt.maErrRef); + } + break; + case svDoubleRef: + { + if (rCxt.mpRefConv) + { + const ScComplexRefData& rRef = *rToken.GetDoubleRef(); + rCxt.mpRefConv->makeRefStr(rLimits, rBuf, rCxt.meGram, rPos, rCxt.maErrRef, rCxt.maTabNames, rRef, false, + bFromRangeName); + } + else + rBuf.append(rCxt.maErrRef); + } + break; + case svMatrix: + { + const ScMatrix* pMat = rToken.GetMatrix(); + if (!pMat) + return; + + size_t nC, nMaxC, nR, nMaxR; + pMat->GetDimensions(nMaxC, nMaxR); + + rBuf.append(rCxt.mxOpCodeMap->getSymbol(ocArrayOpen)); + for (nR = 0 ; nR < nMaxR ; ++nR) + { + if (nR > 0) + { + rBuf.append(rCxt.mxOpCodeMap->getSymbol(ocArrayRowSep)); + } + + for (nC = 0 ; nC < nMaxC ; ++nC) + { + if (nC > 0) + { + rBuf.append(rCxt.mxOpCodeMap->getSymbol(ocArrayColSep)); + } + + if (pMat->IsValue(nC, nR)) + { + if (pMat->IsBoolean(nC, nR)) + { + bool bVal = pMat->GetDouble(nC, nR) != 0.0; + rBuf.append(rCxt.mxOpCodeMap->getSymbol(bVal ? ocTrue : ocFalse)); + } + else + { + FormulaError nErr = pMat->GetError(nC, nR); + if (nErr != FormulaError::NONE) + rBuf.append(ScGlobal::GetErrorString(nErr)); + else + appendDouble(rCxt, rBuf, pMat->GetDouble(nC, nR)); + } + } + else if (pMat->IsEmpty(nC, nR)) + { + // Skip it. + } + else if (pMat->IsStringOrEmpty(nC, nR)) + appendString(rBuf, pMat->GetString(nC, nR).getString()); + } + } + rBuf.append(rCxt.mxOpCodeMap->getSymbol(ocArrayClose)); + } + break; + case svIndex: + { + typedef sc::TokenStringContext::IndexNameMapType NameType; + + sal_uInt16 nIndex = rToken.GetIndex(); + switch (eOp) + { + case ocName: + { + SCTAB nTab = rToken.GetSheet(); + if (nTab < 0) + { + // global named range + NameType::const_iterator it = rCxt.maGlobalRangeNames.find(nIndex); + if (it == rCxt.maGlobalRangeNames.end()) + { + rBuf.append(ScCompiler::GetNativeSymbol(ocErrName)); + break; + } + + rBuf.append(it->second); + } + else + { + // sheet-local named range + if (nTab != rPos.Tab()) + { + // On other sheet. + OUString aName; + if (o3tl::make_unsigned(nTab) < rCxt.maTabNames.size()) + aName = rCxt.maTabNames[nTab]; + if (!aName.isEmpty()) + { + ScCompiler::CheckTabQuotes( aName, rCxt.mpRefConv->meConv); + rBuf.append( aName); + } + else + rBuf.append(ScCompiler::GetNativeSymbol(ocErrName)); + rBuf.append( rCxt.mpRefConv->getSpecialSymbol( ScCompiler::Convention::SHEET_SEPARATOR)); + } + + sc::TokenStringContext::TabIndexMapType::const_iterator itTab = rCxt.maSheetRangeNames.find(nTab); + if (itTab == rCxt.maSheetRangeNames.end()) + { + rBuf.append(ScCompiler::GetNativeSymbol(ocErrName)); + break; + } + + const NameType& rNames = itTab->second; + NameType::const_iterator it = rNames.find(nIndex); + if (it == rNames.end()) + { + rBuf.append(ScCompiler::GetNativeSymbol(ocErrName)); + break; + } + + rBuf.append(it->second); + } + } + break; + case ocDBArea: + case ocTableRef: + { + NameType::const_iterator it = rCxt.maNamedDBs.find(nIndex); + if (it != rCxt.maNamedDBs.end()) + rBuf.append(it->second); + } + break; + default: + rBuf.append(ScCompiler::GetNativeSymbol(ocErrName)); + } + } + break; + case svExternal: + { + // mapped or translated name of AddIns + OUString aAddIn = rToken.GetExternal(); + bool bMapped = rCxt.mxOpCodeMap->isPODF(); // ODF 1.1 directly uses programmatical name + if (!bMapped && rCxt.mxOpCodeMap->hasExternals()) + { + const ExternalHashMap& rExtMap = rCxt.mxOpCodeMap->getReverseExternalHashMap(); + ExternalHashMap::const_iterator it = rExtMap.find(aAddIn); + if (it != rExtMap.end()) + { + aAddIn = it->second; + bMapped = true; + } + } + + if (!bMapped && !rCxt.mxOpCodeMap->isEnglish()) + ScGlobal::GetAddInCollection()->LocalizeString(aAddIn); + + rBuf.append(aAddIn); + } + break; + case svError: + { + FormulaError nErr = rToken.GetError(); + OpCode eOpErr; + switch (nErr) + { + break; + case FormulaError::DivisionByZero: + eOpErr = ocErrDivZero; + break; + case FormulaError::NoValue: + eOpErr = ocErrValue; + break; + case FormulaError::NoRef: + eOpErr = ocErrRef; + break; + case FormulaError::NoName: + eOpErr = ocErrName; + break; + case FormulaError::IllegalFPOperation: + eOpErr = ocErrNum; + break; + case FormulaError::NotAvailable: + eOpErr = ocErrNA; + break; + case FormulaError::NoCode: + default: + eOpErr = ocErrNull; + } + rBuf.append(rCxt.mxOpCodeMap->getSymbol(eOpErr)); + } + break; + case svByte: + case svJump: + case svFAP: + case svMissing: + case svSep: + default: + ; + } +} + +} + +OUString ScTokenArray::CreateString( sc::TokenStringContext& rCxt, const ScAddress& rPos ) const +{ + if (!nLen) + return OUString(); + + OUStringBuffer aBuf; + + FormulaToken** p = pCode.get(); + FormulaToken** pEnd = p + static_cast<size_t>(nLen); + for (; p != pEnd; ++p) + { + const FormulaToken* pToken = *p; + OpCode eOp = pToken->GetOpCode(); + /* FIXME: why does this ignore the count of spaces? */ + if (eOp == ocSpaces) + { + // TODO : Handle intersection operator '!!'. + aBuf.append(' '); + continue; + } + else if (eOp == ocWhitespace) + { + aBuf.append( pToken->GetChar()); + continue; + } + + if (eOp < rCxt.mxOpCodeMap->getSymbolCount()) + aBuf.append(rCxt.mxOpCodeMap->getSymbol(eOp)); + + appendTokenByType(*mxSheetLimits, rCxt, aBuf, *pToken, rPos, IsFromRangeName()); + } + + return aBuf.makeStringAndClear(); +} + +namespace { + +void wrapAddress( ScAddress& rPos, SCCOL nMaxCol, SCROW nMaxRow ) +{ + if (rPos.Col() > nMaxCol) + rPos.SetCol(rPos.Col() % (nMaxCol+1)); + if (rPos.Row() > nMaxRow) + rPos.SetRow(rPos.Row() % (nMaxRow+1)); +} + +template<typename T> void wrapRange( T& n1, T& n2, T nMax ) +{ + if (n2 > nMax) + { + if (n1 == 0) + n2 = nMax; // Truncate to full range instead of wrapping to a weird range. + else + n2 = n2 % (nMax+1); + } + if (n1 > nMax) + n1 = n1 % (nMax+1); +} + +void wrapColRange( ScRange& rRange, SCCOL nMaxCol ) +{ + SCCOL nCol1 = rRange.aStart.Col(); + SCCOL nCol2 = rRange.aEnd.Col(); + wrapRange( nCol1, nCol2, nMaxCol); + rRange.aStart.SetCol( nCol1); + rRange.aEnd.SetCol( nCol2); +} + +void wrapRowRange( ScRange& rRange, SCROW nMaxRow ) +{ + SCROW nRow1 = rRange.aStart.Row(); + SCROW nRow2 = rRange.aEnd.Row(); + wrapRange( nRow1, nRow2, nMaxRow); + rRange.aStart.SetRow( nRow1); + rRange.aEnd.SetRow( nRow2); +} + +} + +void ScTokenArray::WrapReference( const ScAddress& rPos, SCCOL nMaxCol, SCROW nMaxRow ) +{ + FormulaToken** p = pCode.get(); + FormulaToken** pEnd = p + static_cast<size_t>(nLen); + for (; p != pEnd; ++p) + { + switch ((*p)->GetType()) + { + case svSingleRef: + { + formula::FormulaToken* pToken = *p; + ScSingleRefData& rRef = *pToken->GetSingleRef(); + ScAddress aAbs = rRef.toAbs(*mxSheetLimits, rPos); + wrapAddress(aAbs, nMaxCol, nMaxRow); + rRef.SetAddress(*mxSheetLimits, aAbs, rPos); + } + break; + case svDoubleRef: + { + formula::FormulaToken* pToken = *p; + ScComplexRefData& rRef = *pToken->GetDoubleRef(); + ScRange aAbs = rRef.toAbs(*mxSheetLimits, rPos); + // Entire columns/rows are sticky. + if (!rRef.IsEntireCol(*mxSheetLimits) && !rRef.IsEntireRow(*mxSheetLimits)) + { + wrapColRange( aAbs, nMaxCol); + wrapRowRange( aAbs, nMaxRow); + } + else if (rRef.IsEntireCol(*mxSheetLimits) && !rRef.IsEntireRow(*mxSheetLimits)) + wrapColRange( aAbs, nMaxCol); + else if (!rRef.IsEntireCol(*mxSheetLimits) && rRef.IsEntireRow(*mxSheetLimits)) + wrapRowRange( aAbs, nMaxRow); + // else nothing if both, column and row, are entire. + aAbs.PutInOrder(); + rRef.SetRange(*mxSheetLimits, aAbs, rPos); + } + break; + default: + ; + } + } +} + +sal_Int32 ScTokenArray::GetWeight() const +{ + sal_Int32 nResult = 0; + for (auto i = 0; i < nRPN; ++i) + { + switch ((*pRPN[i]).GetType()) + { + case svDoubleRef: + { + const auto pComplexRef = (*pRPN[i]).GetDoubleRef(); + + // Number of cells referenced divided by 10. + const double nRows = 1 + (pComplexRef->Ref2.Row() - pComplexRef->Ref1.Row()); + const double nCols = 1 + (pComplexRef->Ref2.Col() - pComplexRef->Ref1.Col()); + const double nNumCellsTerm = nRows * nCols / 10.0; + + if (nNumCellsTerm + nResult < SAL_MAX_INT32) + nResult += nNumCellsTerm; + else + nResult = SAL_MAX_INT32; + } + break; + default: + ; + } + } + + if (nResult == 0) + nResult = 1; + + return nResult; +} + +#if DEBUG_FORMULA_COMPILER + +void ScTokenArray::Dump() const +{ + cout << "+++ Normal Tokens +++" << endl; + for (sal_uInt16 i = 0; i < nLen; ++i) + { + DumpToken(*pCode[i]); + } + + cout << "+++ RPN Tokens +++" << endl; + for (sal_uInt16 i = 0; i < nRPN; ++i) + { + DumpToken(*pRPN[i]); + } +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/tokenstringcontext.cxx b/sc/source/core/tool/tokenstringcontext.cxx new file mode 100644 index 0000000000..5a4430c155 --- /dev/null +++ b/sc/source/core/tool/tokenstringcontext.cxx @@ -0,0 +1,136 @@ +/* -*- 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/. + */ + +#include <tokenstringcontext.hxx> +#include <compiler.hxx> +#include <document.hxx> +#include <dbdata.hxx> +#include <externalrefmgr.hxx> +#include <globstr.hrc> +#include <scresid.hxx> + +using namespace com::sun::star; + +namespace sc { + +namespace { + +void insertAllNames( TokenStringContext::IndexNameMapType& rMap, const ScRangeName& rNames ) +{ + for (auto const& it : rNames) + { + const ScRangeData *const pData = it.second.get(); + rMap.emplace(pData->GetIndex(), pData->GetName()); + } +} + +} + +TokenStringContext::TokenStringContext( const ScDocument& rDoc, formula::FormulaGrammar::Grammar eGram ) : + meGram(eGram), + mpRefConv(ScCompiler::GetRefConvention(formula::FormulaGrammar::extractRefConvention(eGram))) +{ + formula::FormulaCompiler aComp; + mxOpCodeMap = aComp.GetOpCodeMap(formula::FormulaGrammar::extractFormulaLanguage(eGram)); + if (mxOpCodeMap) + maErrRef = mxOpCodeMap->getSymbol(ocErrRef); + else + { + assert(!"TokenStringContext - no OpCodeMap?!?"); + maErrRef = ScResId(STR_NO_REF_TABLE); + } + + // Fetch all sheet names. + maTabNames = rDoc.GetAllTableNames(); + { + for (auto& rTabName : maTabNames) + ScCompiler::CheckTabQuotes(rTabName, formula::FormulaGrammar::extractRefConvention(eGram)); + } + + // Fetch all named range names. + const ScRangeName* pNames = rDoc.GetRangeName(); + if (pNames) + // global names + insertAllNames(maGlobalRangeNames, *pNames); + + { + ScRangeName::TabNameCopyMap aTabRangeNames; + rDoc.GetAllTabRangeNames(aTabRangeNames); + for (const auto& [nTab, pSheetNames] : aTabRangeNames) + { + if (!pSheetNames) + continue; + + IndexNameMapType aNames; + insertAllNames(aNames, *pSheetNames); + maSheetRangeNames.emplace(nTab, aNames); + } + } + + // Fetch all named database ranges names. + const ScDBCollection* pDBs = rDoc.GetDBCollection(); + if (pDBs) + { + const ScDBCollection::NamedDBs& rNamedDBs = pDBs->getNamedDBs(); + for (const auto& rxNamedDB : rNamedDBs) + { + const ScDBData& rData = *rxNamedDB; + maNamedDBs.emplace(rData.GetIndex(), rData.GetName()); + } + } + + // Fetch all relevant bits for external references. + if (!rDoc.HasExternalRefManager()) + return; + + const ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); + maExternalFileNames = pRefMgr->getAllCachedExternalFileNames(); + for (size_t i = 0, n = maExternalFileNames.size(); i < n; ++i) + { + sal_uInt16 nFileId = static_cast<sal_uInt16>(i); + std::vector<OUString> aTabNames; + pRefMgr->getAllCachedTableNames(nFileId, aTabNames); + if (!aTabNames.empty()) + maExternalCachedTabNames.emplace(nFileId, aTabNames); + } +} + +CompileFormulaContext::CompileFormulaContext( ScDocument& rDoc ) : + mrDoc(rDoc), meGram(rDoc.GetGrammar()) +{ + updateTabNames(); +} + +CompileFormulaContext::CompileFormulaContext( ScDocument& rDoc, formula::FormulaGrammar::Grammar eGram ) : + mrDoc(rDoc), meGram(eGram) +{ + updateTabNames(); +} + +void CompileFormulaContext::updateTabNames() +{ + // Fetch all sheet names. + maTabNames = mrDoc.GetAllTableNames(); + { + for (auto& rTabName : maTabNames) + ScCompiler::CheckTabQuotes(rTabName, formula::FormulaGrammar::extractRefConvention(meGram)); + } +} + +void CompileFormulaContext::setGrammar( formula::FormulaGrammar::Grammar eGram ) +{ + bool bUpdate = (meGram != eGram); + meGram = eGram; + if (bUpdate) + updateTabNames(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/typedstrdata.cxx b/sc/source/core/tool/typedstrdata.cxx new file mode 100644 index 0000000000..e00c1bc18d --- /dev/null +++ b/sc/source/core/tool/typedstrdata.cxx @@ -0,0 +1,128 @@ +/* -*- 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/. + */ + +#include <typedstrdata.hxx> +#include <global.hxx> + +#include <unotools/collatorwrapper.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <utility> + +bool ScTypedStrData::LessHiddenRows::operator() (const ScTypedStrData& left, const ScTypedStrData& right) const +{ + return left.mbIsHiddenByFilter < right.mbIsHiddenByFilter; +} + +bool ScTypedStrData::LessCaseSensitive::operator() (const ScTypedStrData& left, const ScTypedStrData& right) const +{ + if (left.meStrType != right.meStrType) + return left.meStrType < right.meStrType; + + if (left.meStrType == Value) + { + if (left.mfValue == right.mfValue) + return left.mbIsHiddenByFilter < right.mbIsHiddenByFilter; + return left.mfValue < right.mfValue; + } + + if (left.mbIsDate != right.mbIsDate) + return left.mbIsDate < right.mbIsDate; + + sal_Int32 nEqual = ScGlobal::GetCaseCollator().compareString( + left.maStrValue, right.maStrValue); + + if (!nEqual) + return left.mbIsHiddenByFilter < right.mbIsHiddenByFilter; + + return nEqual < 0; +} + +bool ScTypedStrData::LessCaseInsensitive::operator() (const ScTypedStrData& left, const ScTypedStrData& right) const +{ + if (left.meStrType != right.meStrType) + return left.meStrType < right.meStrType; + + if (left.meStrType == Value) + { + if (left.mfValue == right.mfValue) + return left.mbIsHiddenByFilter < right.mbIsHiddenByFilter; + return left.mfValue < right.mfValue; + } + + if (left.mbIsDate != right.mbIsDate) + return left.mbIsDate < right.mbIsDate; + + sal_Int32 nEqual = ScGlobal::GetCollator().compareString( + left.maStrValue, right.maStrValue); + + if (!nEqual) + return left.mbIsHiddenByFilter < right.mbIsHiddenByFilter; + + return nEqual < 0; +} + +bool ScTypedStrData::EqualCaseSensitive::operator() (const ScTypedStrData& left, const ScTypedStrData& right) const +{ + if (left.meStrType != right.meStrType) + return false; + + if (left.meStrType == Value && left.mfRoundedValue != right.mfRoundedValue) + return false; + + if (left.mbIsDate != right.mbIsDate ) + return false; + + return ScGlobal::GetCaseTransliteration().isEqual(left.maStrValue, right.maStrValue); +} + +bool ScTypedStrData::EqualCaseInsensitive::operator() (const ScTypedStrData& left, const ScTypedStrData& right) const +{ + if (left.meStrType != right.meStrType) + return false; + + if (left.meStrType == Value && left.mfRoundedValue != right.mfRoundedValue) + return false; + + if (left.mbIsDate != right.mbIsDate ) + return false; + + return ScGlobal::GetTransliteration().isEqual(left.maStrValue, right.maStrValue); +} + +bool ScTypedStrData::operator< (const ScTypedStrData& r) const +{ + // Case insensitive comparison by default. + return LessCaseInsensitive()(*this, r); +} + +ScTypedStrData::ScTypedStrData( + const OUString& rStr, double fVal, double fRVal, StringType nType, bool bDate, bool bIsHiddenByFilter ) : + maStrValue(rStr), + mfValue(fVal), + mfRoundedValue(fRVal), + meStrType(nType), + mbIsDate( bDate ), + mbIsHiddenByFilter(bIsHiddenByFilter) {} + +FindTypedStrData::FindTypedStrData(ScTypedStrData aVal, bool bCaseSens) : + maVal(std::move(aVal)), mbCaseSens(bCaseSens) {} + +bool FindTypedStrData::operator() (const ScTypedStrData& r) const +{ + if (mbCaseSens) + { + return ScTypedStrData::EqualCaseSensitive()(maVal, r); + } + else + { + return ScTypedStrData::EqualCaseInsensitive()(maVal, r); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/unitconv.cxx b/sc/source/core/tool/unitconv.cxx new file mode 100644 index 0000000000..1f5337cd81 --- /dev/null +++ b/sc/source/core/tool/unitconv.cxx @@ -0,0 +1,118 @@ +/* -*- 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 <com/sun/star/uno/Sequence.hxx> + +#include <unitconv.hxx> +#include <global.hxx> +#include <optutil.hxx> + +using namespace utl; +using namespace com::sun::star::uno; + +const sal_Unicode cDelim = 0x01; // delimiter between From and To + +// ScUnitConverterData +ScUnitConverterData::ScUnitConverterData( + std::u16string_view rFromUnit, std::u16string_view rToUnit, double fValue ) : + maIndexString(BuildIndexString(rFromUnit, rToUnit)), + mfValue(fValue) {} + +OUString ScUnitConverterData::BuildIndexString( + std::u16string_view rFromUnit, std::u16string_view rToUnit ) +{ + return rFromUnit + OUStringChar(cDelim) + rToUnit; +} + +// ScUnitConverter +constexpr OUStringLiteral CFGPATH_UNIT = u"Office.Calc/UnitConversion"; +constexpr OUStringLiteral CFGSTR_UNIT_FROM = u"FromUnit"; +constexpr OUStringLiteral CFGSTR_UNIT_TO = u"ToUnit"; +constexpr OUStringLiteral CFGSTR_UNIT_FACTOR = u"Factor"; + +ScUnitConverter::ScUnitConverter() +{ + // read from configuration - "convert.ini" is no longer used + //TODO: config item as member to allow change of values + + ScLinkConfigItem aConfigItem( CFGPATH_UNIT ); + + // empty node name -> use the config item's path itself + const Sequence<OUString> aNodeNames = aConfigItem.GetNodeNames( "" ); + + tools::Long nNodeCount = aNodeNames.getLength(); + if ( !nNodeCount ) + return; + + Sequence<OUString> aValNames( nNodeCount * 3 ); + OUString* pValNameArray = aValNames.getArray(); + const OUString sSlash('/'); + + tools::Long nIndex = 0; + for (const OUString& rNode : aNodeNames) + { + OUString sPrefix = rNode + sSlash; + + pValNameArray[nIndex++] = sPrefix + CFGSTR_UNIT_FROM; + pValNameArray[nIndex++] = sPrefix + CFGSTR_UNIT_TO; + pValNameArray[nIndex++] = sPrefix + CFGSTR_UNIT_FACTOR; + } + + Sequence<Any> aProperties = aConfigItem.GetProperties(aValNames); + + if (aProperties.getLength() != aValNames.getLength()) + return; + + const Any* pProperties = aProperties.getConstArray(); + + OUString sFromUnit; + OUString sToUnit; + double fFactor = 0; + + nIndex = 0; + for (tools::Long i=0; i<nNodeCount; i++) + { + pProperties[nIndex++] >>= sFromUnit; + pProperties[nIndex++] >>= sToUnit; + pProperties[nIndex++] >>= fFactor; + + ScUnitConverterData aNew(sFromUnit, sToUnit, fFactor); + OUString const aIndex = aNew.GetIndexString(); + maData.insert(std::make_pair(aIndex, aNew)); + } +} + +ScUnitConverter::~ScUnitConverter() {} + +bool ScUnitConverter::GetValue( + double& fValue, std::u16string_view rFromUnit, std::u16string_view rToUnit ) const +{ + OUString aIndex = ScUnitConverterData::BuildIndexString(rFromUnit, rToUnit); + MapType::const_iterator it = maData.find(aIndex); + if (it == maData.end()) + { + fValue = 1.0; + return false; + } + + fValue = it->second.GetValue(); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/userlist.cxx b/sc/source/core/tool/userlist.cxx new file mode 100644 index 0000000000..b1030eca62 --- /dev/null +++ b/sc/source/core/tool/userlist.cxx @@ -0,0 +1,248 @@ +/* -*- 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 <sal/config.h> + +#include <unotools/charclass.hxx> + +#include <global.hxx> +#include <userlist.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <com/sun/star/i18n/Calendar2.hpp> + +#include <algorithm> +#include <utility> + +ScUserListData::SubStr::SubStr(OUString&& aReal) : + maReal(std::move(aReal)), maUpper(ScGlobal::getCharClass().uppercase(maReal)) {} + +void ScUserListData::InitTokens() +{ + maSubStrings.clear(); + sal_Int32 nIndex = 0; + do + { + OUString aSub = aStr.getToken(0, ScGlobal::cListDelimiter, nIndex); + if (!aSub.isEmpty()) + maSubStrings.emplace_back(std::move(aSub)); + } while (nIndex >= 0); +} + +ScUserListData::ScUserListData(OUString _aStr) : + aStr(std::move(_aStr)) +{ + InitTokens(); +} + +void ScUserListData::SetString( const OUString& rStr ) +{ + aStr = rStr; + InitTokens(); +} + +bool ScUserListData::GetSubIndex(const OUString& rSubStr, sal_uInt16& rIndex, bool& bMatchCase) const +{ + // First, case sensitive search. + auto itr = ::std::find_if(maSubStrings.begin(), maSubStrings.end(), + [&rSubStr](const SubStr& item) { return item.maReal == rSubStr; }); + if (itr != maSubStrings.end()) + { + rIndex = ::std::distance(maSubStrings.begin(), itr); + bMatchCase = true; + return true; + } + + // When that fails, do a case insensitive search. + bMatchCase = false; + OUString aUpStr = ScGlobal::getCharClass().uppercase(rSubStr); + itr = ::std::find_if(maSubStrings.begin(), maSubStrings.end(), + [&aUpStr](const SubStr& item) { return item.maUpper == aUpStr; }); + if (itr != maSubStrings.end()) + { + rIndex = ::std::distance(maSubStrings.begin(), itr); + return true; + } + return false; +} + +OUString ScUserListData::GetSubStr(sal_uInt16 nIndex) const +{ + if (nIndex < maSubStrings.size()) + return maSubStrings[nIndex].maReal; + else + return OUString(); +} + +sal_Int32 ScUserListData::Compare(const OUString& rSubStr1, const OUString& rSubStr2) const +{ + sal_uInt16 nIndex1, nIndex2; + bool bMatchCase; + bool bFound1 = GetSubIndex(rSubStr1, nIndex1, bMatchCase); + bool bFound2 = GetSubIndex(rSubStr2, nIndex2, bMatchCase); + if (bFound1) + { + if (bFound2) + { + if (nIndex1 < nIndex2) + return -1; + else if (nIndex1 > nIndex2) + return 1; + else + return 0; + } + else + return -1; + } + else if (bFound2) + return 1; + else + return ScGlobal::GetCaseTransliteration().compareString( rSubStr1, rSubStr2 ); +} + +sal_Int32 ScUserListData::ICompare(const OUString& rSubStr1, const OUString& rSubStr2) const +{ + sal_uInt16 nIndex1, nIndex2; + bool bMatchCase; + bool bFound1 = GetSubIndex(rSubStr1, nIndex1, bMatchCase); + bool bFound2 = GetSubIndex(rSubStr2, nIndex2, bMatchCase); + if (bFound1) + { + if (bFound2) + { + if (nIndex1 < nIndex2) + return -1; + else if (nIndex1 > nIndex2) + return 1; + else + return 0; + } + else + return -1; + } + else if (bFound2) + return 1; + else + return ScGlobal::GetTransliteration().compareString( rSubStr1, rSubStr2 ); +} + +ScUserList::ScUserList(bool initDefault) +{ + if (initDefault) + AddDefaults(); +} + +void ScUserList::AddDefaults() +{ + sal_Unicode cDelimiter = ScGlobal::cListDelimiter; + for (const auto& rCalendar : ScGlobal::getLocaleData().getAllCalendars()) + { + if (const auto& xCal = rCalendar.Days; xCal.hasElements()) + { + OUStringBuffer aDayShortBuf(32), aDayLongBuf(64); + sal_Int32 i; + sal_Int32 nLen = xCal.getLength(); + sal_Int16 nStart = sal::static_int_cast<sal_Int16>(nLen); + while (nStart > 0) + { + if (xCal[--nStart].ID == rCalendar.StartOfWeek) + break; + } + sal_Int16 nLast = sal::static_int_cast<sal_Int16>( (nStart + nLen - 1) % nLen ); + for (i = nStart; i != nLast; i = (i+1) % nLen) + { + aDayShortBuf.append(xCal[i].AbbrevName); + aDayShortBuf.append(cDelimiter); + aDayLongBuf.append(xCal[i].FullName); + aDayLongBuf.append(cDelimiter); + } + aDayShortBuf.append(xCal[i].AbbrevName); + aDayLongBuf.append(xCal[i].FullName); + + OUString aDayShort = aDayShortBuf.makeStringAndClear(); + OUString aDayLong = aDayLongBuf.makeStringAndClear(); + + if ( !HasEntry( aDayShort ) ) + emplace_back(aDayShort); + if ( !HasEntry( aDayLong ) ) + emplace_back(aDayLong); + } + + if (const auto& xCal = rCalendar.Months; xCal.hasElements()) + { + OUStringBuffer aMonthShortBuf(128), aMonthLongBuf(128); + sal_Int32 i; + sal_Int32 nLen = xCal.getLength() - 1; + for (i = 0; i < nLen; i++) + { + aMonthShortBuf.append(xCal[i].AbbrevName); + aMonthShortBuf.append(cDelimiter); + aMonthLongBuf.append(xCal[i].FullName); + aMonthLongBuf.append(cDelimiter); + } + aMonthShortBuf.append(xCal[i].AbbrevName); + aMonthLongBuf.append(xCal[i].FullName); + + OUString aMonthShort = aMonthShortBuf.makeStringAndClear(); + OUString aMonthLong = aMonthLongBuf.makeStringAndClear(); + + if ( !HasEntry( aMonthShort ) ) + emplace_back(aMonthShort); + if ( !HasEntry( aMonthLong ) ) + emplace_back(aMonthLong); + } + } +} + +const ScUserListData* ScUserList::GetData(const OUString& rSubStr) const +{ + const ScUserListData* pFirstCaseInsensitive = nullptr; + sal_uInt16 nIndex; + bool bMatchCase = false; + + for (const auto& rxItem : maData) + { + if (rxItem.GetSubIndex(rSubStr, nIndex, bMatchCase)) + { + if (bMatchCase) + return &rxItem; + if (!pFirstCaseInsensitive) + pFirstCaseInsensitive = &rxItem; + } + } + + return pFirstCaseInsensitive; +} + +bool ScUserList::operator==( const ScUserList& r ) const +{ + return std::equal(maData.begin(), maData.end(), r.maData.begin(), r.maData.end(), + [](const ScUserListData& lhs, const ScUserListData& rhs) { + return lhs.GetString() == rhs.GetString(); + }); +} + +bool ScUserList::HasEntry( std::u16string_view rStr ) const +{ + return ::std::any_of(maData.begin(), maData.end(), + [&] (ScUserListData const& pData) + { return pData.GetString() == rStr; } ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/viewopti.cxx b/sc/source/core/tool/viewopti.cxx new file mode 100644 index 0000000000..39e2e6e0e7 --- /dev/null +++ b/sc/source/core/tool/viewopti.cxx @@ -0,0 +1,593 @@ +/* -*- 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 <osl/diagnose.h> + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> + +#include <svtools/colorcfg.hxx> + +#include <global.hxx> +#include <viewopti.hxx> +#include <sc.hrc> +#include <scmod.hxx> +#include <miscuno.hxx> + +using namespace utl; +using namespace com::sun::star::uno; + + + +void ScGridOptions::SetDefaults() +{ + *this = ScGridOptions(); + + // grid defaults differ now between the apps + // therefore, enter here in its own right (all in 1/100mm) + + if ( ScOptionsUtil::IsMetricSystem() ) + { + nFldDrawX = 1000; // 1cm + nFldDrawY = 1000; + } + else + { + nFldDrawX = 1270; // 0,5" + nFldDrawY = 1270; + } + nFldDivisionX = 1; + nFldDivisionY = 1; +} + +bool ScGridOptions::operator==( const ScGridOptions& rCpy ) const +{ + return ( nFldDrawX == rCpy.nFldDrawX + && nFldDivisionX == rCpy.nFldDivisionX + && nFldDrawY == rCpy.nFldDrawY + && nFldDivisionY == rCpy.nFldDivisionY + && bUseGridsnap == rCpy.bUseGridsnap + && bSynchronize == rCpy.bSynchronize + && bGridVisible == rCpy.bGridVisible + && bEqualGrid == rCpy.bEqualGrid ); +} + + +ScViewOptions::ScViewOptions() +{ + SetDefaults(); +} + +ScViewOptions::ScViewOptions( const ScViewOptions& rCpy ) +{ + *this = rCpy; +} + +ScViewOptions::~ScViewOptions() +{ +} + +void ScViewOptions::SetDefaults() +{ + aOptArr[ VOPT_FORMULAS ] = false; + aOptArr[ VOPT_SYNTAX ] = false; + aOptArr[ VOPT_HELPLINES ] = false; + aOptArr[ VOPT_GRID_ONTOP ] = false; + aOptArr[ VOPT_NOTES ] = true; + aOptArr[ VOPT_FORMULAS_MARKS ] = false; + aOptArr[ VOPT_NULLVALS ] = true; + aOptArr[ VOPT_VSCROLL ] = true; + aOptArr[ VOPT_HSCROLL ] = true; + aOptArr[ VOPT_TABCONTROLS ] = true; + aOptArr[ VOPT_OUTLINER ] = true; + aOptArr[ VOPT_HEADER ] = true; + aOptArr[ VOPT_GRID ] = true; + aOptArr[ VOPT_ANCHOR ] = true; + aOptArr[ VOPT_PAGEBREAKS ] = true; + aOptArr[ VOPT_SUMMARY ] = true; + aOptArr[ VOPT_COPY_SHEET ] = false; + aOptArr[ VOPT_THEMEDCURSOR ] = false; + + aModeArr[VOBJ_TYPE_OLE ] = VOBJ_MODE_SHOW; + aModeArr[VOBJ_TYPE_CHART] = VOBJ_MODE_SHOW; + aModeArr[VOBJ_TYPE_DRAW ] = VOBJ_MODE_SHOW; + + aGridCol = svtools::ColorConfig().GetColorValue( svtools::CALCGRID ).nColor; + + aDocCol = SC_MOD()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor; + + aGridOpt.SetDefaults(); +} + +Color const & ScViewOptions::GetGridColor( OUString* pStrName ) const +{ + if ( pStrName ) + *pStrName = aGridColName; + + return aGridCol; +} + +ScViewOptions& ScViewOptions::operator=(const ScViewOptions& rCpy) = default; + +bool ScViewOptions::operator==( const ScViewOptions& rOpt ) const +{ + bool bEqual = true; + sal_uInt16 i; + + for ( i=0; i<MAX_OPT && bEqual; i++ ) bEqual = (aOptArr [i] == rOpt.aOptArr[i]); + for ( i=0; i<MAX_TYPE && bEqual; i++ ) bEqual = (aModeArr[i] == rOpt.aModeArr[i]); + + bEqual = bEqual && (aGridCol == rOpt.aGridCol); + bEqual = bEqual && (aGridColName == rOpt.aGridColName); + bEqual = bEqual && (aGridOpt == rOpt.aGridOpt); + bEqual = bEqual && (sColorSchemeName == rOpt.sColorSchemeName); + bEqual = bEqual && (aDocCol == rOpt.aDocCol); + + return bEqual; +} + +std::unique_ptr<SvxGridItem> ScViewOptions::CreateGridItem() const +{ + std::unique_ptr<SvxGridItem> pItem(new SvxGridItem( SID_ATTR_GRID_OPTIONS )); + + pItem->SetFieldDrawX ( aGridOpt.GetFieldDrawX() ); + pItem->SetFieldDivisionX ( aGridOpt.GetFieldDivisionX() ); + pItem->SetFieldDrawY ( aGridOpt.GetFieldDrawY() ); + pItem->SetFieldDivisionY ( aGridOpt.GetFieldDivisionY() ); + pItem->SetUseGridSnap ( aGridOpt.GetUseGridSnap() ); + pItem->SetSynchronize ( aGridOpt.GetSynchronize() ); + pItem->SetGridVisible ( aGridOpt.GetGridVisible() ); + pItem->SetEqualGrid ( aGridOpt.GetEqualGrid() ); + + return pItem; +} + +// ScTpViewItem - data for the ViewOptions TabPage + +ScTpViewItem::ScTpViewItem( const ScViewOptions& rOpt ) + : SfxPoolItem ( SID_SCVIEWOPTIONS ), + theOptions ( rOpt ) +{ +} + +ScTpViewItem::~ScTpViewItem() +{ +} + +bool ScTpViewItem::operator==( const SfxPoolItem& rItem ) const +{ + assert(SfxPoolItem::operator==(rItem)); + + const ScTpViewItem& rPItem = static_cast<const ScTpViewItem&>(rItem); + + return ( theOptions == rPItem.theOptions ); +} + +ScTpViewItem* ScTpViewItem::Clone( SfxItemPool * ) const +{ + return new ScTpViewItem( *this ); +} + +// Config Item containing view options + +constexpr OUStringLiteral CFGPATH_LAYOUT = u"Office.Calc/Layout"; + +#define SCLAYOUTOPT_GRIDLINES 0 +#define SCLAYOUTOPT_GRIDCOLOR 1 +#define SCLAYOUTOPT_PAGEBREAK 2 +#define SCLAYOUTOPT_GUIDE 3 +#define SCLAYOUTOPT_COLROWHDR 4 +#define SCLAYOUTOPT_HORISCROLL 5 +#define SCLAYOUTOPT_VERTSCROLL 6 +#define SCLAYOUTOPT_SHEETTAB 7 +#define SCLAYOUTOPT_OUTLINE 8 +#define SCLAYOUTOPT_GRID_ONCOLOR 9 +#define SCLAYOUTOPT_SUMMARY 10 +#define SCLAYOUTOPT_THEMEDCURSOR 11 + +constexpr OUStringLiteral CFGPATH_DISPLAY = u"Office.Calc/Content/Display"; + +#define SCDISPLAYOPT_FORMULA 0 +#define SCDISPLAYOPT_ZEROVALUE 1 +#define SCDISPLAYOPT_NOTETAG 2 +#define SCDISPLAYOPT_FORMULAMARK 3 +#define SCDISPLAYOPT_VALUEHI 4 +#define SCDISPLAYOPT_ANCHOR 5 +#define SCDISPLAYOPT_OBJECTGRA 6 +#define SCDISPLAYOPT_CHART 7 +#define SCDISPLAYOPT_DRAWING 8 + +constexpr OUStringLiteral CFGPATH_GRID = u"Office.Calc/Grid"; + +#define SCGRIDOPT_RESOLU_X 0 +#define SCGRIDOPT_RESOLU_Y 1 +#define SCGRIDOPT_SUBDIV_X 2 +#define SCGRIDOPT_SUBDIV_Y 3 +#define SCGRIDOPT_SNAPTOGRID 4 +#define SCGRIDOPT_SYNCHRON 5 +#define SCGRIDOPT_VISIBLE 6 +#define SCGRIDOPT_SIZETOGRID 7 + +Sequence<OUString> ScViewCfg::GetLayoutPropertyNames() +{ + return {"Line/GridLine", // SCLAYOUTOPT_GRIDLINES + "Line/GridLineColor", // SCLAYOUTOPT_GRIDCOLOR + "Line/PageBreak", // SCLAYOUTOPT_PAGEBREAK + "Line/Guide", // SCLAYOUTOPT_GUIDE + "Window/ColumnRowHeader", // SCLAYOUTOPT_COLROWHDR + "Window/HorizontalScroll", // SCLAYOUTOPT_HORISCROLL + "Window/VerticalScroll", // SCLAYOUTOPT_VERTSCROLL + "Window/SheetTab", // SCLAYOUTOPT_SHEETTAB + "Window/OutlineSymbol", // SCLAYOUTOPT_OUTLINE + "Line/GridOnColoredCells", // SCLAYOUTOPT_GRID_ONCOLOR; + "Window/SearchSummary", // SCLAYOUTOPT_SUMMARY + "Window/ThemedCursor"}; // SCLAYOUTOPT_THEMEDCURSOR +} + +Sequence<OUString> ScViewCfg::GetDisplayPropertyNames() +{ + return {"Formula", // SCDISPLAYOPT_FORMULA + "ZeroValue", // SCDISPLAYOPT_ZEROVALUE + "NoteTag", // SCDISPLAYOPT_NOTETAG + "FormulaMark", // SCDISPLAYOPT_FORMULAMARK + "ValueHighlighting", // SCDISPLAYOPT_VALUEHI + "Anchor", // SCDISPLAYOPT_ANCHOR + "ObjectGraphic", // SCDISPLAYOPT_OBJECTGRA + "Chart", // SCDISPLAYOPT_CHART + "DrawingObject"}; // SCDISPLAYOPT_DRAWING; +} + +Sequence<OUString> ScViewCfg::GetGridPropertyNames() +{ + const bool bIsMetric = ScOptionsUtil::IsMetricSystem(); + + return {(bIsMetric ? OUString("Resolution/XAxis/Metric") + : OUString("Resolution/XAxis/NonMetric")), // SCGRIDOPT_RESOLU_X + (bIsMetric ? OUString("Resolution/YAxis/Metric") + : OUString("Resolution/YAxis/NonMetric")), // SCGRIDOPT_RESOLU_Y + "Subdivision/XAxis", // SCGRIDOPT_SUBDIV_X + "Subdivision/YAxis", // SCGRIDOPT_SUBDIV_Y + "Option/SnapToGrid", // SCGRIDOPT_SNAPTOGRID + "Option/Synchronize", // SCGRIDOPT_SYNCHRON + "Option/VisibleGrid", // SCGRIDOPT_VISIBLE + "Option/SizeToGrid"}; // SCGRIDOPT_SIZETOGRID; +} + +ScViewCfg::ScViewCfg() : + aLayoutItem( CFGPATH_LAYOUT ), + aDisplayItem( CFGPATH_DISPLAY ), + aGridItem( CFGPATH_GRID ) +{ + sal_Int32 nIntVal = 0; + + Sequence<OUString> aNames = GetLayoutPropertyNames(); + Sequence<Any> aValues = aLayoutItem.GetProperties(aNames); + aLayoutItem.EnableNotification(aNames); + const Any* pValues = aValues.getConstArray(); + OSL_ENSURE(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if(aValues.getLength() == aNames.getLength()) + { + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + OSL_ENSURE(pValues[nProp].hasValue(), "property value missing"); + if(pValues[nProp].hasValue()) + { + switch(nProp) + { + case SCLAYOUTOPT_GRIDCOLOR: + { + Color aColor; + if ( pValues[nProp] >>= aColor ) + SetGridColor( aColor, OUString() ); + break; + } + case SCLAYOUTOPT_GRIDLINES: + SetOption( VOPT_GRID, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCLAYOUTOPT_GRID_ONCOLOR: + SetOption( VOPT_GRID_ONTOP, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCLAYOUTOPT_PAGEBREAK: + SetOption( VOPT_PAGEBREAKS, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCLAYOUTOPT_GUIDE: + SetOption( VOPT_HELPLINES, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCLAYOUTOPT_COLROWHDR: + SetOption( VOPT_HEADER, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCLAYOUTOPT_HORISCROLL: + SetOption( VOPT_HSCROLL, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCLAYOUTOPT_VERTSCROLL: + SetOption( VOPT_VSCROLL, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCLAYOUTOPT_SHEETTAB: + SetOption( VOPT_TABCONTROLS, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCLAYOUTOPT_OUTLINE: + SetOption( VOPT_OUTLINER, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCLAYOUTOPT_SUMMARY: + SetOption( VOPT_SUMMARY, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCLAYOUTOPT_THEMEDCURSOR: + SetOption( VOPT_THEMEDCURSOR, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + } + } + } + } + aLayoutItem.SetCommitLink( LINK( this, ScViewCfg, LayoutCommitHdl ) ); + + aNames = GetDisplayPropertyNames(); + aValues = aDisplayItem.GetProperties(aNames); + aDisplayItem.EnableNotification(aNames); + pValues = aValues.getConstArray(); + OSL_ENSURE(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if(aValues.getLength() == aNames.getLength()) + { + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + OSL_ENSURE(pValues[nProp].hasValue(), "property value missing"); + if(pValues[nProp].hasValue()) + { + switch(nProp) + { + case SCDISPLAYOPT_FORMULA: + SetOption( VOPT_FORMULAS, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCDISPLAYOPT_ZEROVALUE: + SetOption( VOPT_NULLVALS, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCDISPLAYOPT_NOTETAG: + SetOption( VOPT_NOTES, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCDISPLAYOPT_FORMULAMARK: + SetOption( VOPT_FORMULAS_MARKS, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCDISPLAYOPT_VALUEHI: + SetOption( VOPT_SYNTAX, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCDISPLAYOPT_ANCHOR: + SetOption( VOPT_ANCHOR, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCDISPLAYOPT_OBJECTGRA: + if ( pValues[nProp] >>= nIntVal ) + { + //#i80528# adapt to new range eventually + if(sal_Int32(VOBJ_MODE_HIDE) < nIntVal) nIntVal = sal_Int32(VOBJ_MODE_SHOW); + + SetObjMode( VOBJ_TYPE_OLE, static_cast<ScVObjMode>(nIntVal)); + } + break; + case SCDISPLAYOPT_CHART: + if ( pValues[nProp] >>= nIntVal ) + { + //#i80528# adapt to new range eventually + if(sal_Int32(VOBJ_MODE_HIDE) < nIntVal) nIntVal = sal_Int32(VOBJ_MODE_SHOW); + + SetObjMode( VOBJ_TYPE_CHART, static_cast<ScVObjMode>(nIntVal)); + } + break; + case SCDISPLAYOPT_DRAWING: + if ( pValues[nProp] >>= nIntVal ) + { + //#i80528# adapt to new range eventually + if(sal_Int32(VOBJ_MODE_HIDE) < nIntVal) nIntVal = sal_Int32(VOBJ_MODE_SHOW); + + SetObjMode( VOBJ_TYPE_DRAW, static_cast<ScVObjMode>(nIntVal)); + } + break; + } + } + } + } + aDisplayItem.SetCommitLink( LINK( this, ScViewCfg, DisplayCommitHdl ) ); + + ScGridOptions aGrid = GetGridOptions(); //TODO: initialization necessary? + aNames = GetGridPropertyNames(); + aValues = aGridItem.GetProperties(aNames); + aGridItem.EnableNotification(aNames); + pValues = aValues.getConstArray(); + OSL_ENSURE(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if(aValues.getLength() == aNames.getLength()) + { + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + OSL_ENSURE(pValues[nProp].hasValue(), "property value missing"); + if(pValues[nProp].hasValue()) + { + switch(nProp) + { + case SCGRIDOPT_RESOLU_X: + if (pValues[nProp] >>= nIntVal) aGrid.SetFieldDrawX( nIntVal ); + break; + case SCGRIDOPT_RESOLU_Y: + if (pValues[nProp] >>= nIntVal) aGrid.SetFieldDrawY( nIntVal ); + break; + case SCGRIDOPT_SUBDIV_X: + if (pValues[nProp] >>= nIntVal) aGrid.SetFieldDivisionX( nIntVal ); + break; + case SCGRIDOPT_SUBDIV_Y: + if (pValues[nProp] >>= nIntVal) aGrid.SetFieldDivisionY( nIntVal ); + break; + case SCGRIDOPT_SNAPTOGRID: + aGrid.SetUseGridSnap( ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCGRIDOPT_SYNCHRON: + aGrid.SetSynchronize( ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCGRIDOPT_VISIBLE: + aGrid.SetGridVisible( ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + case SCGRIDOPT_SIZETOGRID: + aGrid.SetEqualGrid( ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) ); + break; + } + } + } + } + SetGridOptions( aGrid ); + aGridItem.SetCommitLink( LINK( this, ScViewCfg, GridCommitHdl ) ); +} + +IMPL_LINK_NOARG(ScViewCfg, LayoutCommitHdl, ScLinkConfigItem&, void) +{ + Sequence<OUString> aNames = GetLayoutPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + switch(nProp) + { + case SCLAYOUTOPT_GRIDCOLOR: + pValues[nProp] <<= GetGridColor(); + break; + case SCLAYOUTOPT_GRIDLINES: + pValues[nProp] <<= GetOption( VOPT_GRID ); + break; + case SCLAYOUTOPT_GRID_ONCOLOR: + pValues[nProp] <<= GetOption( VOPT_GRID_ONTOP ); + break; + case SCLAYOUTOPT_PAGEBREAK: + pValues[nProp] <<= GetOption( VOPT_PAGEBREAKS ); + break; + case SCLAYOUTOPT_GUIDE: + pValues[nProp] <<= GetOption( VOPT_HELPLINES ); + break; + case SCLAYOUTOPT_COLROWHDR: + pValues[nProp] <<= GetOption( VOPT_HEADER ); + break; + case SCLAYOUTOPT_HORISCROLL: + pValues[nProp] <<= GetOption( VOPT_HSCROLL ); + break; + case SCLAYOUTOPT_VERTSCROLL: + pValues[nProp] <<= GetOption( VOPT_VSCROLL ); + break; + case SCLAYOUTOPT_SHEETTAB: + pValues[nProp] <<= GetOption( VOPT_TABCONTROLS ); + break; + case SCLAYOUTOPT_OUTLINE: + pValues[nProp] <<= GetOption( VOPT_OUTLINER ); + break; + case SCLAYOUTOPT_SUMMARY: + pValues[nProp] <<= GetOption( VOPT_SUMMARY ); + break; + case SCLAYOUTOPT_THEMEDCURSOR: + pValues[nProp] <<= GetOption( VOPT_THEMEDCURSOR ); + break; + } + } + aLayoutItem.PutProperties(aNames, aValues); +} + +IMPL_LINK_NOARG(ScViewCfg, DisplayCommitHdl, ScLinkConfigItem&, void) +{ + Sequence<OUString> aNames = GetDisplayPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + switch(nProp) + { + case SCDISPLAYOPT_FORMULA: + pValues[nProp] <<= GetOption( VOPT_FORMULAS ); + break; + case SCDISPLAYOPT_ZEROVALUE: + pValues[nProp] <<= GetOption( VOPT_NULLVALS ); + break; + case SCDISPLAYOPT_NOTETAG: + pValues[nProp] <<= GetOption( VOPT_NOTES ); + break; + case SCDISPLAYOPT_FORMULAMARK: + pValues[nProp] <<= GetOption( VOPT_FORMULAS_MARKS ); + break; + case SCDISPLAYOPT_VALUEHI: + pValues[nProp] <<= GetOption( VOPT_SYNTAX ); + break; + case SCDISPLAYOPT_ANCHOR: + pValues[nProp] <<= GetOption( VOPT_ANCHOR ); + break; + case SCDISPLAYOPT_OBJECTGRA: + pValues[nProp] <<= static_cast<sal_Int32>(GetObjMode( VOBJ_TYPE_OLE )); + break; + case SCDISPLAYOPT_CHART: + pValues[nProp] <<= static_cast<sal_Int32>(GetObjMode( VOBJ_TYPE_CHART )); + break; + case SCDISPLAYOPT_DRAWING: + pValues[nProp] <<= static_cast<sal_Int32>(GetObjMode( VOBJ_TYPE_DRAW )); + break; + } + } + aDisplayItem.PutProperties(aNames, aValues); +} + +IMPL_LINK_NOARG(ScViewCfg, GridCommitHdl, ScLinkConfigItem&, void) +{ + const ScGridOptions& rGrid = GetGridOptions(); + + Sequence<OUString> aNames = GetGridPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + switch(nProp) + { + case SCGRIDOPT_RESOLU_X: + pValues[nProp] <<= static_cast<sal_Int32>(rGrid.GetFieldDrawX()); + break; + case SCGRIDOPT_RESOLU_Y: + pValues[nProp] <<= static_cast<sal_Int32>(rGrid.GetFieldDrawY()); + break; + case SCGRIDOPT_SUBDIV_X: + pValues[nProp] <<= static_cast<sal_Int32>(rGrid.GetFieldDivisionX()); + break; + case SCGRIDOPT_SUBDIV_Y: + pValues[nProp] <<= static_cast<sal_Int32>(rGrid.GetFieldDivisionY()); + break; + case SCGRIDOPT_SNAPTOGRID: + pValues[nProp] <<= rGrid.GetUseGridSnap(); + break; + case SCGRIDOPT_SYNCHRON: + pValues[nProp] <<= rGrid.GetSynchronize(); + break; + case SCGRIDOPT_VISIBLE: + pValues[nProp] <<= rGrid.GetGridVisible(); + break; + case SCGRIDOPT_SIZETOGRID: + pValues[nProp] <<= rGrid.GetEqualGrid(); + break; + } + } + aGridItem.PutProperties(aNames, aValues); +} + +void ScViewCfg::SetOptions( const ScViewOptions& rNew ) +{ + *static_cast<ScViewOptions*>(this) = rNew; + aLayoutItem.SetModified(); + aDisplayItem.SetModified(); + aGridItem.SetModified(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/tool/webservicelink.cxx b/sc/source/core/tool/webservicelink.cxx new file mode 100644 index 0000000000..b61907471e --- /dev/null +++ b/sc/source/core/tool/webservicelink.cxx @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <comphelper/processfactory.hxx> +#include <sfx2/linkmgr.hxx> +#include <sfx2/bindings.hxx> + +#include <com/sun/star/ucb/XSimpleFileAccess3.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <com/sun/star/io/XInputStream.hpp> + +#include <utility> +#include <webservicelink.hxx> +#include <brdcst.hxx> +#include <document.hxx> +#include <sc.hrc> + +ScWebServiceLink::ScWebServiceLink(ScDocument* pD, OUString _aURL) + : ::sfx2::SvBaseLink(SfxLinkUpdateMode::ALWAYS, SotClipboardFormatId::STRING) + , pDoc(pD) + , aURL(std::move(_aURL)) + , bHasResult(false) +{ +} + +ScWebServiceLink::~ScWebServiceLink() {} + +sfx2::SvBaseLink::UpdateResult ScWebServiceLink::DataChanged(const OUString&, const css::uno::Any&) +{ + aResult.clear(); + bHasResult = false; + + css::uno::Reference<css::ucb::XSimpleFileAccess3> xFileAccess + = css::ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext()); + if (!xFileAccess.is()) + return ERROR_GENERAL; + + css::uno::Reference<css::io::XInputStream> xStream; + try + { + xStream = xFileAccess->openFileRead(aURL); + } + catch (...) + { + // don't let any exceptions pass + return ERROR_GENERAL; + } + if (!xStream.is()) + return ERROR_GENERAL; + + const sal_Int32 BUF_LEN = 8000; + css::uno::Sequence<sal_Int8> buffer(BUF_LEN); + OStringBuffer aBuffer(64000); + + sal_Int32 nRead = 0; + while ((nRead = xStream->readBytes(buffer, BUF_LEN)) == BUF_LEN) + aBuffer.append(reinterpret_cast<const char*>(buffer.getConstArray()), nRead); + + if (nRead > 0) + aBuffer.append(reinterpret_cast<const char*>(buffer.getConstArray()), nRead); + + xStream->closeInput(); + + aResult = OStringToOUString(aBuffer, RTL_TEXTENCODING_UTF8); + bHasResult = true; + + // Something happened... + if (HasListeners()) + { + Broadcast(ScHint(SfxHintId::ScDataChanged, ScAddress())); + pDoc->TrackFormulas(); // must happen immediately + pDoc->StartTrackTimer(); + } + + return SUCCESS; +} + +void ScWebServiceLink::ListenersGone() +{ + ScDocument* pStackDoc = pDoc; // member pDoc can't be used after removing the link + + sfx2::LinkManager* pLinkMgr = pDoc->GetLinkManager(); + pLinkMgr->Remove(this); // deletes this + + if (pLinkMgr->GetLinks().empty()) // deleted the last one ? + { + SfxBindings* pBindings = pStackDoc->GetViewBindings(); // don't use member pDoc! + if (pBindings) + pBindings->Invalidate(SID_LINKS); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sc/source/core/tool/zforauto.cxx b/sc/source/core/tool/zforauto.cxx new file mode 100644 index 0000000000..f6fb26cba1 --- /dev/null +++ b/sc/source/core/tool/zforauto.cxx @@ -0,0 +1,88 @@ +/* -*- 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 <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <svl/zformat.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <osl/diagnose.h> + +#include <zforauto.hxx> +#include <tools/stream.hxx> + +ScNumFormatAbbrev::ScNumFormatAbbrev() : + sFormatstring ( "Standard" ), + eLanguage (LANGUAGE_SYSTEM), + eSysLanguage (LANGUAGE_GERMAN) // otherwise "Standard" does not fit +{ +} + +ScNumFormatAbbrev::ScNumFormatAbbrev(sal_uInt32 nFormat, + const SvNumberFormatter& rFormatter) +{ + PutFormatIndex(nFormat, rFormatter); +} + +void ScNumFormatAbbrev::Load( SvStream& rStream, rtl_TextEncoding eByteStrSet ) +{ + sal_uInt16 nSysLang, nLang; + sFormatstring = rStream.ReadUniOrByteString( eByteStrSet ); + rStream.ReadUInt16( nSysLang ).ReadUInt16( nLang ); + eLanguage = LanguageType(nLang); + eSysLanguage = LanguageType(nSysLang); + if ( eSysLanguage == LANGUAGE_SYSTEM ) // old versions did write it + eSysLanguage = Application::GetSettings().GetLanguageTag().getLanguageType(); +} + +void ScNumFormatAbbrev::Save( SvStream& rStream, rtl_TextEncoding eByteStrSet ) const +{ + rStream.WriteUniOrByteString( sFormatstring, eByteStrSet ); + rStream.WriteUInt16( static_cast<sal_uInt16>(eSysLanguage) ).WriteUInt16( static_cast<sal_uInt16>(eLanguage) ); +} + +void ScNumFormatAbbrev::PutFormatIndex(sal_uInt32 nFormat, + const SvNumberFormatter& rFormatter) +{ + const SvNumberformat* pFormat = rFormatter.GetEntry(nFormat); + if (pFormat) + { + eSysLanguage = Application::GetSettings().GetLanguageTag().getLanguageType(); + eLanguage = pFormat->GetLanguage(); + sFormatstring = pFormat->GetFormatstring(); + } + else + { + OSL_FAIL("SCNumFormatAbbrev:: unknown number format"); + eLanguage = LANGUAGE_SYSTEM; + eSysLanguage = LANGUAGE_GERMAN; // otherwise "Standard" does not fit + sFormatstring = "Standard"; + } +} + +sal_uInt32 ScNumFormatAbbrev::GetFormatIndex( SvNumberFormatter& rFormatter) +{ + SvNumFormatType nType; + bool bNewInserted; + sal_Int32 nCheckPos; + return rFormatter.GetIndexPuttingAndConverting( sFormatstring, eLanguage, + eSysLanguage, nType, bNewInserted, nCheckPos); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |