summaryrefslogtreecommitdiffstats
path: root/sc/source/core/tool
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sc/source/core/tool
parentInitial commit. (diff)
downloadlibreoffice-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')
-rw-r--r--sc/source/core/tool/addincfg.cxx53
-rw-r--r--sc/source/core/tool/addincol.cxx1731
-rw-r--r--sc/source/core/tool/addinhelpid.cxx210
-rw-r--r--sc/source/core/tool/addinlis.cxx135
-rw-r--r--sc/source/core/tool/address.cxx2531
-rw-r--r--sc/source/core/tool/adiasync.cxx137
-rw-r--r--sc/source/core/tool/appoptio.cxx668
-rw-r--r--sc/source/core/tool/arraysumSSE2.cxx120
-rw-r--r--sc/source/core/tool/autoform.cxx934
-rw-r--r--sc/source/core/tool/calcconfig.cxx241
-rw-r--r--sc/source/core/tool/callform.cxx412
-rw-r--r--sc/source/core/tool/cellform.cxx217
-rw-r--r--sc/source/core/tool/cellkeytranslator.cxx225
-rw-r--r--sc/source/core/tool/cellkeywords.inl199
-rw-r--r--sc/source/core/tool/chartarr.cxx374
-rw-r--r--sc/source/core/tool/charthelper.cxx432
-rw-r--r--sc/source/core/tool/chartlis.cxx630
-rw-r--r--sc/source/core/tool/chartlock.cxx180
-rw-r--r--sc/source/core/tool/chartpos.cxx524
-rw-r--r--sc/source/core/tool/chgtrack.cxx4682
-rw-r--r--sc/source/core/tool/chgviset.cxx150
-rw-r--r--sc/source/core/tool/compare.cxx334
-rw-r--r--sc/source/core/tool/compiler.cxx6651
-rw-r--r--sc/source/core/tool/consoli.cxx544
-rw-r--r--sc/source/core/tool/dbdata.cxx1656
-rw-r--r--sc/source/core/tool/ddelink.cxx266
-rw-r--r--sc/source/core/tool/defaultsoptions.cxx152
-rw-r--r--sc/source/core/tool/detdata.cxx87
-rw-r--r--sc/source/core/tool/detfunc.cxx1653
-rw-r--r--sc/source/core/tool/docoptio.cxx371
-rw-r--r--sc/source/core/tool/doubleref.cxx474
-rw-r--r--sc/source/core/tool/editdataarray.cxx75
-rw-r--r--sc/source/core/tool/editutil.cxx941
-rw-r--r--sc/source/core/tool/filtopt.cxx74
-rw-r--r--sc/source/core/tool/formulagroup.cxx244
-rw-r--r--sc/source/core/tool/formulalogger.cxx352
-rw-r--r--sc/source/core/tool/formulaopt.cxx653
-rw-r--r--sc/source/core/tool/formulaparserpool.cxx143
-rw-r--r--sc/source/core/tool/formularesult.cxx641
-rw-r--r--sc/source/core/tool/grouparealistener.cxx352
-rw-r--r--sc/source/core/tool/hints.cxx114
-rw-r--r--sc/source/core/tool/inputopt.cxx157
-rw-r--r--sc/source/core/tool/interpr1.cxx10332
-rw-r--r--sc/source/core/tool/interpr2.cxx3727
-rw-r--r--sc/source/core/tool/interpr3.cxx5585
-rw-r--r--sc/source/core/tool/interpr4.cxx4839
-rw-r--r--sc/source/core/tool/interpr5.cxx3303
-rw-r--r--sc/source/core/tool/interpr6.cxx1010
-rw-r--r--sc/source/core/tool/interpr7.cxx557
-rw-r--r--sc/source/core/tool/interpr8.cxx2008
-rw-r--r--sc/source/core/tool/interpretercontext.cxx219
-rw-r--r--sc/source/core/tool/jumpmatrix.cxx273
-rw-r--r--sc/source/core/tool/listenerquery.cxx90
-rw-r--r--sc/source/core/tool/lookupcache.cxx127
-rw-r--r--sc/source/core/tool/math.cxx72
-rw-r--r--sc/source/core/tool/matrixoperators.cxx41
-rw-r--r--sc/source/core/tool/navicfg.cxx60
-rw-r--r--sc/source/core/tool/numformat.cxx71
-rw-r--r--sc/source/core/tool/odffmap.cxx142
-rw-r--r--sc/source/core/tool/optutil.cxx67
-rw-r--r--sc/source/core/tool/orcusxml.cxx31
-rw-r--r--sc/source/core/tool/parclass.cxx710
-rw-r--r--sc/source/core/tool/printopt.cxx131
-rw-r--r--sc/source/core/tool/prnsave.cxx127
-rw-r--r--sc/source/core/tool/progress.cxx178
-rw-r--r--sc/source/core/tool/queryentry.cxx207
-rw-r--r--sc/source/core/tool/queryparam.cxx464
-rw-r--r--sc/source/core/tool/rangecache.cxx198
-rw-r--r--sc/source/core/tool/rangelst.cxx1531
-rw-r--r--sc/source/core/tool/rangenam.cxx900
-rw-r--r--sc/source/core/tool/rangeseq.cxx451
-rw-r--r--sc/source/core/tool/rangeutl.cxx1067
-rw-r--r--sc/source/core/tool/rechead.cxx148
-rw-r--r--sc/source/core/tool/recursionhelper.cxx304
-rw-r--r--sc/source/core/tool/refdata.cxx599
-rw-r--r--sc/source/core/tool/reffind.cxx334
-rw-r--r--sc/source/core/tool/refhint.cxx80
-rw-r--r--sc/source/core/tool/refreshtimer.cxx140
-rw-r--r--sc/source/core/tool/reftokenhelper.cxx486
-rw-r--r--sc/source/core/tool/refupdat.cxx592
-rw-r--r--sc/source/core/tool/scmatrix.cxx3645
-rw-r--r--sc/source/core/tool/scopetools.cxx122
-rw-r--r--sc/source/core/tool/sharedformula.cxx442
-rw-r--r--sc/source/core/tool/sharedstringpoolpurge.cxx58
-rw-r--r--sc/source/core/tool/stringutil.cxx469
-rw-r--r--sc/source/core/tool/stylehelper.cxx173
-rw-r--r--sc/source/core/tool/subtotal.cxx204
-rw-r--r--sc/source/core/tool/token.cxx5413
-rw-r--r--sc/source/core/tool/tokenstringcontext.cxx136
-rw-r--r--sc/source/core/tool/typedstrdata.cxx128
-rw-r--r--sc/source/core/tool/unitconv.cxx118
-rw-r--r--sc/source/core/tool/userlist.cxx248
-rw-r--r--sc/source/core/tool/viewopti.cxx593
-rw-r--r--sc/source/core/tool/webservicelink.cxx99
-rw-r--r--sc/source/core/tool/zforauto.cxx88
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: */